Permissions d'acquisition audio et vidéo
-
Utiliser le micro ou la caméra requiert des permissions sensibles :
- android.permission.RECORD_AUDIO
- android.permission.RECORD_VIDEO
- Nécessité de déclarer ces permissions dans le manifeste ET de demander explicitement l'accord par un dialogue lors de l'exécution
- Depuis Android 12, l'utilisateur peut désactiver son micro et/ou sa caméra globalement
MediaRecorder
API
- Permet d'enregistrer du son et de la vidéo
-
Pour enregistrer du son (et facultativement de la vidéo) :
- MediaRecorder mr = new MediaRecorder() ;
- mr.setAudioSource(MediaRecorder.AudioSource.{DEFAULT, MIC, VOICE_CALL, VOICE_COMMUNICATION, VOICE_RECOGNITION, VOICE_DOWNLINK, VOICE_UPLINK})
- mr.setVideoSource(MediaRecorder.VideoSource.{DEFAULT, CAMERA})
- mr.setOutputFormat(MediaRecorder.OutputFormat.{DEFAULT, THREE_GP, MPEG_4, AMR_NB, AMR_WB, AAC_ADTS, ...})
- mr.setAudioEncoder(MediaRecoarder.AudioEncoder.{DEFAULT, AAC, AMR_NB, AMR_WB, ...})
- mr.setVideoEncoder(MediaRecorder.VideoEncoder.{DEFAULT, H263, H264})
- mr.setOutputFile(path)
- mr.prepare()
- mr.start() ;
- ...
- mr.stop() :
- mr.release() ;
Exemple
Exemple de l'utilisation de MediaRecorder pour enregistrer une vidéo de l'écran de l'appareil en utilisant MediaProjector :
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.coursand.screenrecorder
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.display.DisplayManager
import android.media.MediaRecorder
import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.annotation.RequiresApi
import android.util.DisplayMetrics
import android.hardware.display.VirtualDisplay
import android.os.Environment
import androidx.core.content.ContextCompat
import android.widget.Toast
import fr.upem.coursand.R
import fr.upem.coursand.launcher.ActivityMetadata
import kotlinx.android.synthetic.main.activity_screen_recorder.*
import java.io.File
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@ActivityMetadata(permissions= [Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO], minApi=Build.VERSION_CODES.LOLLIPOP)
class ScreenRecorderActivity : AppCompatActivity() {
val CAPTURE_REQUEST_ID = 1
private val mediaProjectionManager by lazy { getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager }
private var mediaProjection: MediaProjection? = null
private val displayMetrics by lazy {
val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
metrics
}
private val mediaRecorder by lazy { MediaRecorder() }
private var virtualDisplay: VirtualDisplay? = null
private var mediaProjectionCallback = object: MediaProjection.Callback() {
override fun onStop() {
super.onStop()
stopMediaRecorder()
}
}
var name: String? = null
private var temporaryFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_screen_recorder)
startButton.setOnClickListener { createMediaProjector() }
stopButton.setOnClickListener { stopMediaRecorder() }
recordAudioView.isEnabled = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
val recordable = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
startButton.isEnabled = recordable
if (! recordable)
Toast.makeText(this, "The permission WRITE_EXTERNAL_STORAGE is not granted", Toast.LENGTH_SHORT).show()
}
private fun createMediaProjector() {
if (mediaProjection != null) return // nothing to do it is already created
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), CAPTURE_REQUEST_ID)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == CAPTURE_REQUEST_ID && resultCode == Activity.RESULT_OK && data != null) {
val mp = mediaProjectionManager.getMediaProjection(resultCode, data)
mediaProjection = mp
mp.registerCallback(mediaProjectionCallback, null)
startMediaRecorder(nameView.text.toString())
}
}
private fun startMediaRecorder(givenName: String) {
startButton.isEnabled = false
stopButton.isEnabled = true
mediaRecorder.apply {
val audio = recordAudioView.isChecked
if (audio) setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
val tmpFile = File.createTempFile("screenRecord", ".mp4", Environment.getExternalStorageDirectory())
temporaryFile = tmpFile
name = givenName
setOutputFile(tmpFile.absolutePath)
setVideoSize(displayMetrics.widthPixels, displayMetrics.heightPixels)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
if (audio) setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
setVideoEncodingBitRate(512 * 1000)
setVideoFrameRate(30)
prepare()
virtualDisplay = mediaProjection?.createVirtualDisplay("screenRecorder",
displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.surface, null, null)
start()
}
}
private fun stopMediaRecorder() {
mediaRecorder.stop()
mediaRecorder.reset()
mediaProjection?.apply {
unregisterCallback(mediaProjectionCallback)
stop()
}
virtualDisplay?.release()
virtualDisplay = null
mediaProjection = null
val uri = saveInMediaStore(MediaType.VIDEO, "video/mp4", name!!, temporaryFile!!)
Toast.makeText(this, "Saved to $uri", Toast.LENGTH_SHORT).show()
startButton.isEnabled = true
stopButton.isEnabled = false
}
}
Camera
Appel de l'application caméra
Exemple d'activité appelant l'application caméra de l'appareil pour enregister une photo ou vidéo.
Ne nécessite pas de permission spécifique
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.coursand.camera;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import fr.upem.coursand.R;
/**
* An example of the use of Intent to launch the camera application to take pictures.
* Note that this activity does not require any special permission since it does
* not use directly the camera API.
*/
public class CameraCaller extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_caller);
}
private final static int IMAGE_CAPTURE_REQUEST_CODE = 1;
private final static int VIDEO_CAPTURE_REQUEST_CODE = 2;
public void startCameraActivity(boolean video)
{
Intent intent = new Intent((video)?MediaStore.ACTION_VIDEO_CAPTURE:MediaStore.ACTION_IMAGE_CAPTURE);
// We can also use MediaStore.ACTION_IMAGE_CAPTURE_SECURE to capture an image in lock mode
// To capture a video, we use MediaStore.ACTION_VIDEO_CAPTURE
File pictDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), this.getClass().getSimpleName());
pictDir.mkdir();
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + (video ? ".mp4":".jpg");
File dest = new File(pictDir, timestamp); // Destination file
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(dest)); // Specify the destination file
// Now, we start the picture capture activity
try {
startActivityForResult(intent, (video) ? VIDEO_CAPTURE_REQUEST_CODE : IMAGE_CAPTURE_REQUEST_CODE);
} catch (ActivityNotFoundException e)
{
Log.e(getClass().getName(), "Cannot start the activity due to an exception", e);
Toast.makeText(this, "An exception encountered: " + e, Toast.LENGTH_SHORT).show();
}
}
public void requestPicture(View v)
{
startCameraActivity(false);
}
public void requestVideo(View v)
{
startCameraActivity(true);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (requestCode)
{
case IMAGE_CAPTURE_REQUEST_CODE:
switch (resultCode)
{
case RESULT_OK:
Toast.makeText(this, "Image saved to " + data.getData(), Toast.LENGTH_LONG).show();
// ((ImageView)findViewById(R.id.imageCaptureView)).setImageURI(data.getData());
break;
case RESULT_CANCELED: default:
Toast.makeText(this, "Capture of image failed", Toast.LENGTH_SHORT).show();
}
break;
case VIDEO_CAPTURE_REQUEST_CODE:
switch (resultCode)
{
case RESULT_OK:
Toast.makeText(this, "Video saved to " + data.getData(), Toast.LENGTH_LONG).show();
break;
case RESULT_CANCELED: default:
Toast.makeText(this, "Capture of video failed", Toast.LENGTH_SHORT).show();
}
}
}
}
Usage de l'API caméra
-
Combien de caméras ? Informations
- Camera.getNumberOfCameras()
- Camera.getCameraInfo(int cameraid, CameraInfo i)
-
Ouvrir une caméra : Camera.open(int cameraid)
- Appel à Camera.open() synchrone pouvant bloquer quelques secondes
- Préférable d'utiliser l'API Camera dans une thread secondaire
- Récupération et fixation des paramètres : getParameters(), setParameters(Camera.Parameters)
- Ne pas oublier d'appeler release() pour la libérer
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
Prévisualisation de caméra
-
Où envoyer les données de prévisualisation ?
- Sur une texture OpenGL : setPreviewTexture(SurfaceTexture st)
- Sur un SurfaceHolder (typiquement obtenu avec SurfaceView.getHolder()) : setPreviewDisplay(SurfaceHolder h)
-
Sur une méthode callback : setPreviewCallback(Camera.PreviewCallback cb)
- setPreviewFormat(int) définit le format binaire des previews (par défaut NV21)
- PreviewCallback.onPreviewFrame(byte[] data, Camera c) doit être implanté
-
Contrôle de la prévisualisation
- startPreview() pour démarrer
- stopPreview() pour arrêter
Zoom et capture
-
Contrôle du zoom
- getMaxZoom() : zoom maximal (grand angle=0)
- setZoom(int value) : fixation du zoom
- startSmoothZoom(int value), stopSmoothZoom() : zoom progressif (possibilité d'utiliser un listener)
-
Capture
- takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg)
- shutter.onShutter() sert à signaler la prise de photo (on peut jouer par exemple un son)
- Chaque PictureCallback est optionnel (peut être null) selon les données souhaitées : brutes, post-traitées ou compressées en JPEG. La méthode onPictureTaken(byte[] data, Camera camera) doit être implantée.