Add audio enable/disable feature

Relnote: "Add AudioConfig class to handle the audio related setting
while recording video. The @RequiresPermission annotation is moved
from startRecording functions to AudioConfig to avoid unnecessary
permission requests for the cases that audio is not needed."

Bug: 209528390
Test: run CameraControllerTest, VideoCaptureDeviceTest
& CameraControllerFragmentTest

Change-Id: I28755ca547aa91ee5d4de3440ab9e691c9a856a7
diff --git a/camera/camera-view/api/public_plus_experimental_current.txt b/camera/camera-view/api/public_plus_experimental_current.txt
index 0f8b2c0..a371cb4 100644
--- a/camera/camera-view/api/public_plus_experimental_current.txt
+++ b/camera/camera-view/api/public_plus_experimental_current.txt
@@ -45,9 +45,9 @@
     method @MainThread public void setTapToFocusEnabled(boolean);
     method @MainThread @androidx.camera.view.video.ExperimentalVideo public void setVideoCaptureTargetQuality(androidx.camera.video.Quality?);
     method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
-    method @MainThread @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) @androidx.camera.view.video.ExperimentalVideo public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread @RequiresApi(26) @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) @androidx.camera.view.video.ExperimentalVideo public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) @androidx.camera.view.video.ExperimentalVideo public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread @androidx.camera.view.video.ExperimentalVideo public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread @RequiresApi(26) @androidx.camera.view.video.ExperimentalVideo public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread @androidx.camera.view.video.ExperimentalVideo public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
     method @MainThread public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
     method @MainThread public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
     field public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1; // 0x1
@@ -161,6 +161,12 @@
 
 package androidx.camera.view.video {
 
+  @RequiresApi(21) @androidx.camera.view.video.ExperimentalVideo public class AudioConfig {
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.camera.view.video.AudioConfig create(boolean);
+    method public boolean getAudioEnabled();
+    field public static final androidx.camera.view.video.AudioConfig AUDIO_DISABLED;
+  }
+
   @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalVideo {
   }
 
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
index 3d0e7a1..d037ab4 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
@@ -43,6 +43,7 @@
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
 import androidx.camera.view.CameraController.IMAGE_ANALYSIS
 import androidx.camera.view.CameraController.VIDEO_CAPTURE
+import androidx.camera.view.video.AudioConfig
 import androidx.core.util.Consumer
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.rules.ActivityScenarioRule
@@ -135,6 +136,8 @@
 
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
     private val context: Context = ApplicationProvider.getApplicationContext()
+    private val audioEnabled = AudioConfig.create(true)
+    private val audioDisabled = AudioConfig.AUDIO_DISABLED
     private lateinit var previewView: PreviewView
     private lateinit var lifecycleOwner: FakeLifecycleOwner
     private lateinit var cameraController: LifecycleCameraController
@@ -202,7 +205,7 @@
         val outputOptions = createMediaStoreOutputOptions(resolver)
 
         // Act.
-        recordVideoCompletely(outputOptions)
+        recordVideoCompletely(outputOptions, audioEnabled)
 
         // Verify.
         val uri = finalize.outputResults.outputUri
@@ -222,7 +225,7 @@
         val outputOptions = FileDescriptorOutputOptions.Builder(fileDescriptor).build()
 
         // Act.
-        recordVideoCompletely(outputOptions)
+        recordVideoCompletely(outputOptions, audioEnabled)
 
         // Verify.
         val uri = Uri.fromFile(file)
@@ -240,7 +243,7 @@
         val outputOptions = FileOutputOptions.Builder(file).build()
 
         // Act.
-        recordVideoCompletely(outputOptions)
+        recordVideoCompletely(outputOptions, audioEnabled)
 
         // Verify.
         val uri = Uri.fromFile(file)
@@ -252,13 +255,31 @@
     }
 
     @Test
+    fun canRecordToFile_withoutAudio_whenAudioDisabled() {
+        // Arrange.
+        val file = createTempFile()
+        val outputOptions = FileOutputOptions.Builder(file).build()
+
+        // Act.
+        recordVideoCompletely(outputOptions, audioDisabled)
+
+        // Verify.
+        val uri = Uri.fromFile(file)
+        checkFileOnlyHasVideo(uri)
+        assertThat(finalize.outputResults.outputUri).isEqualTo(uri)
+
+        // Cleanup.
+        file.delete()
+    }
+
+    @Test
     fun canRecordToFile_whenLifecycleStops() {
         // Arrange.
         val file = createTempFile()
         val outputOptions = FileOutputOptions.Builder(file).build()
 
         // Act.
-        recordVideoWithInterruptAction(outputOptions) {
+        recordVideoWithInterruptAction(outputOptions, audioEnabled) {
             instrumentation.runOnMainSync {
                 lifecycleOwner.pauseAndStop()
             }
@@ -281,7 +302,7 @@
         val outputOptions = FileOutputOptions.Builder(file).build()
 
         // Act.
-        recordVideoWithInterruptAction(outputOptions) {
+        recordVideoWithInterruptAction(outputOptions, audioEnabled) {
             instrumentation.runOnMainSync {
                 cameraController.videoCaptureTargetQuality = nextQuality.get()
             }
@@ -304,7 +325,7 @@
         val outputOptions = FileOutputOptions.Builder(file).build()
 
         // Act.
-        recordVideoWithInterruptAction(outputOptions) {
+        recordVideoWithInterruptAction(outputOptions, audioEnabled) {
             instrumentation.runOnMainSync {
                 cameraController.setEnabledUseCases(IMAGE_ANALYSIS)
             }
@@ -330,7 +351,7 @@
 
         // Pre Act.
         latchForVideoSaved = CountDownLatch(VIDEO_SAVED_COUNT_DOWN)
-        recordVideo(outputOptions1)
+        recordVideo(outputOptions1, audioEnabled)
         instrumentation.runOnMainSync {
             activeRecording.stop()
             assertThat(cameraController.isRecording).isFalse()
@@ -338,7 +359,7 @@
 
         // Act.
         instrumentation.runOnMainSync {
-            startRecording(outputOptions2)
+            startRecording(outputOptions2, audioEnabled)
             assertThat(cameraController.isRecording).isTrue()
         }
 
@@ -381,7 +402,7 @@
         val outputOptions = FileOutputOptions.Builder(file).build()
 
         // Act.
-        recordVideoWithInterruptAction(outputOptions) {
+        recordVideoWithInterruptAction(outputOptions, audioEnabled) {
             instrumentation.runOnMainSync {
                 activeRecording.pause()
             }
@@ -413,7 +434,7 @@
         val outputOptions = FileOutputOptions.Builder(file).build()
 
         // Act.
-        recordVideoWithInterruptAction(outputOptions) {
+        recordVideoWithInterruptAction(outputOptions, audioEnabled) {
             instrumentation.runOnMainSync {
                 activeRecording.pause()
             }
@@ -447,11 +468,12 @@
         val outputOptions2 = FileOutputOptions.Builder(file2).build()
 
         // Act.
-        recordVideoWithInterruptAction(outputOptions1) {
+        recordVideoWithInterruptAction(outputOptions1, audioEnabled) {
             instrumentation.runOnMainSync {
                 assertThrows(java.lang.IllegalStateException::class.java) {
                     activeRecording = cameraController.startRecording(
                         outputOptions2,
+                        audioEnabled,
                         CameraXExecutors.directExecutor()
                     ) {}
                 }
@@ -511,9 +533,9 @@
             .build()
     }
 
-    private fun recordVideoCompletely(outputOptions: OutputOptions) {
+    private fun recordVideoCompletely(outputOptions: OutputOptions, audioConfig: AudioConfig) {
         // Act.
-        recordVideoWithInterruptAction(outputOptions) {
+        recordVideoWithInterruptAction(outputOptions, audioConfig) {
             instrumentation.runOnMainSync {
                 activeRecording.stop()
             }
@@ -525,13 +547,14 @@
 
     private fun recordVideoWithInterruptAction(
         outputOptions: OutputOptions,
+        audioConfig: AudioConfig,
         runInterruptAction: () -> Unit
     ) {
         // Arrange.
         latchForVideoSaved = CountDownLatch(VIDEO_SAVED_COUNT_DOWN)
 
         // Act.
-        recordVideo(outputOptions)
+        recordVideo(outputOptions, audioConfig)
         runInterruptAction()
 
         // Verify.
@@ -543,14 +566,14 @@
         }
     }
 
-    private fun recordVideo(outputOptions: OutputOptions) {
+    private fun recordVideo(outputOptions: OutputOptions, audioConfig: AudioConfig) {
         // Arrange.
         latchForVideoStarted = CountDownLatch(VIDEO_STARTED_COUNT_DOWN)
         latchForVideoRecording = CountDownLatch(VIDEO_RECORDING_COUNT_DOWN)
 
         // Act.
         instrumentation.runOnMainSync {
-            startRecording(outputOptions)
+            startRecording(outputOptions, audioConfig)
             assertThat(cameraController.isRecording).isTrue()
         }
 
@@ -562,10 +585,11 @@
     }
 
     @MainThread
-    private fun startRecording(outputOptions: OutputOptions) {
+    private fun startRecording(outputOptions: OutputOptions, audioConfig: AudioConfig) {
         if (outputOptions is FileOutputOptions) {
             activeRecording = cameraController.startRecording(
                 outputOptions,
+                audioConfig,
                 CameraXExecutors.directExecutor(),
                 videoRecordEventListener
             )
@@ -573,6 +597,7 @@
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                 activeRecording = cameraController.startRecording(
                     outputOptions,
+                    audioConfig,
                     CameraXExecutors.directExecutor(),
                     videoRecordEventListener
                 )
@@ -584,6 +609,7 @@
         } else if (outputOptions is MediaStoreOutputOptions) {
             activeRecording = cameraController.startRecording(
                 outputOptions,
+                audioConfig,
                 CameraXExecutors.directExecutor(),
                 videoRecordEventListener
             )
@@ -592,9 +618,14 @@
         }
     }
 
+    private fun checkFileOnlyHasVideo(uri: Uri) {
+        checkFileHasVideo(uri)
+        checkFileHasAudio(uri, false)
+    }
+
     private fun checkFileHasAudioAndVideo(uri: Uri) {
         checkFileHasVideo(uri)
-        checkFileHasAudio(uri)
+        checkFileHasAudio(uri, true)
     }
 
     private fun checkFileHasVideo(uri: Uri) {
@@ -606,13 +637,13 @@
         }
     }
 
-    private fun checkFileHasAudio(uri: Uri) {
+    private fun checkFileHasAudio(uri: Uri, hasAudio: Boolean) {
         val mediaRetriever = MediaMetadataRetriever()
         mediaRetriever.apply {
             setDataSource(context, uri)
             val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO)
 
-            assertThat(value).isEqualTo("yes")
+            assertThat(value).isEqualTo(if (hasAudio) "yes" else null)
         }
     }
 
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index 11cf580..3d95114 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -85,7 +85,9 @@
 import androidx.camera.video.VideoCapture;
 import androidx.camera.video.VideoRecordEvent;
 import androidx.camera.view.transform.OutputTransform;
+import androidx.camera.view.video.AudioConfig;
 import androidx.camera.view.video.ExperimentalVideo;
+import androidx.core.content.PermissionChecker;
 import androidx.core.util.Consumer;
 import androidx.core.util.Preconditions;
 import androidx.lifecycle.LiveData;
@@ -1158,26 +1160,28 @@
      * will be the first event sent to the provided listener, and information about the error can
      * be found in that event's {@link VideoRecordEvent.Finalize#getError()} method.
      *
-     * <p> Recording requires the {@link android.Manifest.permission#RECORD_AUDIO} permission;
-     * without it, starting a recording will fail with a {@link SecurityException}.
+     * <p> Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
+     * permission; without it, starting a recording will fail with a {@link SecurityException}.
      *
      * @param outputOptions the options to store the newly captured video.
+     * @param audioConfig the configuration of audio.
      * @param executor the executor that the event listener will be run on.
      * @param listener the event listener to handle video record events.
      * @return a {@link Recording} that provides controls for new active recordings.
      * @throws IllegalStateException if there is an unfinished active recording.
-     * @throws SecurityException if the {@link android.Manifest.permission#RECORD_AUDIO}
-     * permission is denied.
+     * @throws SecurityException if the audio config specifies audio should be enabled but the
+     * {@link android.Manifest.permission#RECORD_AUDIO} permission is denied.
      */
-    @RequiresPermission(Manifest.permission.RECORD_AUDIO)
+    @SuppressLint("MissingPermission")
     @ExperimentalVideo
     @MainThread
     @NonNull
     public Recording startRecording(
             @NonNull FileOutputOptions outputOptions,
+            @NonNull AudioConfig audioConfig,
             @NonNull Executor executor,
             @NonNull Consumer<VideoRecordEvent> listener) {
-        return startRecordingInternal(outputOptions, executor, listener);
+        return startRecordingInternal(outputOptions, audioConfig, executor, listener);
     }
 
     /**
@@ -1196,27 +1200,29 @@
      * will be the first event sent to the provided listener, and information about the error can
      * be found in that event's {@link VideoRecordEvent.Finalize#getError()} method.
      *
-     * <p> Recording requires the {@link android.Manifest.permission#RECORD_AUDIO} permission;
-     * without it, starting a recording will fail with a {@link SecurityException}.
+     * <p> Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
+     * permission; without it, starting a recording will fail with a {@link SecurityException}.
      *
      * @param outputOptions the options to store the newly captured video.
+     * @param audioConfig the configuration of audio.
      * @param executor the executor that the event listener will be run on.
      * @param listener the event listener to handle video record events.
      * @return a {@link Recording} that provides controls for new active recordings.
      * @throws IllegalStateException if there is an unfinished active recording.
-     * @throws SecurityException if the {@link android.Manifest.permission#RECORD_AUDIO}
-     * permission is denied.
+     * @throws SecurityException if the audio config specifies audio should be enabled but the
+     * {@link android.Manifest.permission#RECORD_AUDIO} permission is denied.
      */
-    @RequiresPermission(Manifest.permission.RECORD_AUDIO)
+    @SuppressLint("MissingPermission")
     @ExperimentalVideo
     @RequiresApi(26)
     @MainThread
     @NonNull
     public Recording startRecording(
             @NonNull FileDescriptorOutputOptions outputOptions,
+            @NonNull AudioConfig audioConfig,
             @NonNull Executor executor,
             @NonNull Consumer<VideoRecordEvent> listener) {
-        return startRecordingInternal(outputOptions, executor, listener);
+        return startRecordingInternal(outputOptions, audioConfig, executor, listener);
     }
 
     /**
@@ -1232,26 +1238,28 @@
      * will be the first event sent to the provided listener, and information about the error can
      * be found in that event's {@link VideoRecordEvent.Finalize#getError()} method.
      *
-     * <p> Recording requires the {@link android.Manifest.permission#RECORD_AUDIO} permission;
-     * without it, starting a recording will fail with a {@link SecurityException}.
+     * <p> Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
+     * permission; without it, starting a recording will fail with a {@link SecurityException}.
      *
      * @param outputOptions the options to store the newly captured video.
+     * @param audioConfig the configuration of audio.
      * @param executor the executor that the event listener will be run on.
      * @param listener the event listener to handle video record events.
      * @return a {@link Recording} that provides controls for new active recordings.
      * @throws IllegalStateException if there is an unfinished active recording.
-     * @throws SecurityException if the {@link android.Manifest.permission#RECORD_AUDIO}
-     * permission is denied.
+     * @throws SecurityException if the audio config specifies audio should be enabled but the
+     * {@link android.Manifest.permission#RECORD_AUDIO} permission is denied.
      */
-    @RequiresPermission(Manifest.permission.RECORD_AUDIO)
+    @SuppressLint("MissingPermission")
     @ExperimentalVideo
     @MainThread
     @NonNull
     public Recording startRecording(
             @NonNull MediaStoreOutputOptions outputOptions,
+            @NonNull AudioConfig audioConfig,
             @NonNull Executor executor,
             @NonNull Consumer<VideoRecordEvent> listener) {
-        return startRecordingInternal(outputOptions, executor, listener);
+        return startRecordingInternal(outputOptions, audioConfig, executor, listener);
     }
 
     @RequiresPermission(Manifest.permission.RECORD_AUDIO)
@@ -1259,6 +1267,7 @@
     @MainThread
     private Recording startRecordingInternal(
             @NonNull OutputOptions outputOptions,
+            @NonNull AudioConfig audioConfig,
             @NonNull Executor executor,
             @NonNull Consumer<VideoRecordEvent> listener) {
         checkMainThread();
@@ -1268,19 +1277,33 @@
 
         Consumer<VideoRecordEvent> wrappedListener =
                 wrapListenerToDeactivateRecordingOnFinalized(listener);
-        PendingRecording pendingRecording = prepareRecording(outputOptions).withAudioEnabled();
+        PendingRecording pendingRecording = prepareRecording(outputOptions);
+        boolean isAudioEnabled = audioConfig.getAudioEnabled();
+        if (isAudioEnabled) {
+            checkAudioPermissionGranted();
+            pendingRecording.withAudioEnabled();
+        }
         Recording recording = pendingRecording.start(executor, wrappedListener);
         setActiveRecording(recording, wrappedListener);
 
         return recording;
     }
 
+    private void checkAudioPermissionGranted() {
+        int permissionState = PermissionChecker.checkSelfPermission(mAppContext,
+                Manifest.permission.RECORD_AUDIO);
+        if (permissionState == PermissionChecker.PERMISSION_DENIED) {
+            throw new SecurityException("Attempted to start recording with audio, but "
+                    + "application does not have RECORD_AUDIO permission granted.");
+        }
+    }
+
     /**
      * Generates a {@link PendingRecording} instance for starting a recording.
      *
      * <p> This method handles {@code prepareRecording()} methods for different output formats,
-     * and makes {@link #startRecordingInternal(OutputOptions, Executor, Consumer)} only handle
-     * the general flow.
+     * and makes {@link #startRecordingInternal(OutputOptions, AudioConfig, Executor, Consumer)}
+     * only handle the general flow.
      *
      * <p> This method uses the parent class {@link OutputOptions} as the parameter. On the other
      * hand, the public {@code startRecording()} is overloaded with subclasses. The reason is to
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/video/AudioConfig.java b/camera/camera-view/src/main/java/androidx/camera/view/video/AudioConfig.java
new file mode 100644
index 0000000..67977b7
--- /dev/null
+++ b/camera/camera-view/src/main/java/androidx/camera/view/video/AudioConfig.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.view.video;
+
+import android.Manifest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+
+/**
+ * A class providing configuration for audio settings in the video recording.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@ExperimentalVideo
+public class AudioConfig {
+
+    /**
+     * The audio configuration with audio disabled.
+     */
+    @NonNull
+    public static final AudioConfig AUDIO_DISABLED = new AudioConfig(false);
+
+    private final boolean mIsAudioEnabled;
+
+    AudioConfig(boolean audioEnabled) {
+        mIsAudioEnabled = audioEnabled;
+    }
+
+    /**
+     * Creates a default {@link AudioConfig} with the given audio enabled state.
+     *
+     * <p> The {@link android.Manifest.permission#RECORD_AUDIO} permission is required to
+     * enable audio in video recording; for the use cases where audio is always disabled, please
+     * use {@link AudioConfig#AUDIO_DISABLED} instead, which has no permission requirements.
+     */
+    @RequiresPermission(Manifest.permission.RECORD_AUDIO)
+    @NonNull
+    public static AudioConfig create(boolean enableAudio) {
+        return new AudioConfig(enableAudio);
+    }
+
+    /**
+     * Get the audio enabled state.
+     */
+    public boolean getAudioEnabled() {
+        return mIsAudioEnabled;
+    }
+}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
index 4ffe7bc..3881ff3 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
@@ -77,6 +77,7 @@
 import androidx.camera.view.LifecycleCameraController;
 import androidx.camera.view.PreviewView;
 import androidx.camera.view.RotationProvider;
+import androidx.camera.view.video.AudioConfig;
 import androidx.camera.view.video.ExperimentalVideo;
 import androidx.core.util.Consumer;
 import androidx.fragment.app.Fragment;
@@ -656,8 +657,9 @@
     @OptIn(markerClass = ExperimentalVideo.class)
     void startRecording(Consumer<VideoRecordEvent> listener) {
         MediaStoreOutputOptions outputOptions = getNewVideoOutputMediaStoreOptions();
-        mActiveRecording = mCameraController.startRecording(outputOptions, mExecutorService,
-                listener);
+        AudioConfig audioConfig = AudioConfig.create(true);
+        mActiveRecording = mCameraController.startRecording(outputOptions, audioConfig,
+                mExecutorService, listener);
     }
 
     @VisibleForTesting