preface

In Android audio and video development, online knowledge is too fragmented, self-learning is very difficult, but audio and video cattle Jhuster put forward “Android audio and video from the entry to improve – task list”. This article is one of the Android audio and video tasks list, corresponding to the content to learn is: video capture – system API foundation


Audio and video task list

Audio and video task list: Click here to jump to view.


directory


(1) Introduction to camera system API

(1.1) Use the default Intent to take photos

Launch the camera with the default Intent

mTakePhotoByIntent.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        // Make sure there is a camera activity to handle intents
        if(intent.resolveActivity(getPackageManager()) ! =null) { startActivityForResult(intent, TAKE_PHOTO_BY_INTENT); }}});Copy the code

We used startActivityForResult() to start the activity, so the result will be returned to the onActivityResult() method after the photo is taken. If the photo was taken successfully, the image can be displayed in onActivityResult.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case TAKE_PHOTO_BY_INTENT:
            if (resultCode == RESULT_OK) {
                Bundle extras = data.getExtras();
                Bitmap imageBitmap = (Bitmap) extras.get("data");
                mPicture.setImageBitmap(imageBitmap);
            }
            break; }}Copy the code

The built-in Camera is basically a built-in system application of every smart phone, which provides an intention filter inside:

<intent-filter>
    <action android:name="android.media.action.IMAGE_CAPTURE"/>
    <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
Copy the code

Therefore, the system camera can be called by constructing the following intents

Intent intent = new Intent("android.media.action.IMAGE_CAPTURE")
Copy the code

It can also be called through the constant ACTION_IMAGE_CAPTURE of the MediaStore class

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Copy the code

When running this example, the image is small, and the Camera returns a small thumbnail because of the small memory of the mobile device


(1.2) Save the image to the cache directory

After the photo is taken, the image is saved to the cache directory

mTakePhotoToCache.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Create a File object to store the image after the photo is taken
        File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
        try {
            if (outputImage.exists()) {
                outputImage.delete();
            }
            outputImage.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }


        if(outputImage ! =null) {
            if (Build.VERSION.SDK_INT < 24) {
                mImageUri = Uri.fromFile(outputImage);
            } else {
                mImageUri = FileProvider.getUriForFile(TakePhotoActivity.this."com.lzacking.camerabasedemo.fileprovider",
                        outputImage);
            }
            // Start the camera program
            Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
            // Make sure there is a camera activity to handle intents
            if(intent.resolveActivity(getPackageManager()) ! =null) {
                intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);// Save the photo to the specified URIstartActivityForResult(intent, TAKE_PHOTO_TO_CACHE); }}}});Copy the code

The onActivityResult() method displays the photo taken

case TAKE_PHOTO_TO_CACHE:
    if (resultCode == RESULT_OK) {
        try {
            // Display the photos taken
            Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(mImageUri));
            mPicture.setImageBitmap(bitmap);
        } catch(Exception e) { e.printStackTrace(); }}break;
Copy the code
  • (1) Create a File object to store the pictures taken by the camera, name the picture output_ image.jpg, and store it in the application associated cache directory of the SD card of the mobile phone.

What is the application associated cache directory? Is specially used to store the current application of cache data in SD card, call getExternalCacheDir () method can get the directory, the specific path is/sdcard/Android/data/cache /.

So why use the app associated cache directory to store images? Since Android6.0, read and write SD card is listed as dangerous permissions, if the picture stored in any other SD card directory, have to run time permission processing, and the use of application associated directory can skip this step.

  • The fromFile() method of Uri is called to convert the File object into a Uri object that identifies the local real path of the image output_image.jpg. Otherwise, the FileProvider’s getUriForFUe() method is called to convert the File object into an encapsulated Uri object.

The getUriForFile() method takes three arguments

(1) The first parameter requires passing to the Context object. (2) The second argument can be any unique string. (3) The third argument is the File object we just created. The reason for this layer conversion is that starting with Android 7.0, using a Uri directly from the local real path is considered unsafe and throws a FileUriExposedException. FileProvider is a special content provider. It uses a similar mechanism to content providers to protect data and selectively share encapsulated URIs to the outside world, thus improving application security.

  • Http://image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media.action.image_capture = android.media. Finally, call startActivityForResult() to start the activity. Since we are using an implicit Intent, the system will find an activity to launch in response to this Intent, so the camera program will be opened and the photo taken will be output to output_image.jpg. If the photo was taken successfully, the image can be displayed in onActivityResult. Call BitmapFactory’s decodeStream() method to parse the output_image.jpg image into a Bitmap object and set it to display in the ImageView.

  • (4) Because the content provider is used, you need to register the content provider in androidmanifest.xml as follows:

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".TakeVideoActivity"></activity>
    <activity android:name=".TakePhotoActivity" />
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.lzacking.camerabasedemo.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
    
</application>
Copy the code

The value of the Android: Name attribute is fixed.

Android: authorities attribute’s value must be and just FileProvider getUriForFile () method of the second parameter. Android: Exported indicates whether other applications are supported to call the current component. Android: grantUriPermissions this property at present I have not yet fully understood, such as fully understood, after adding the provider of the label for internal use meta – data sharing of tag specifies the Uri path, and cites a @ XML/fite_paths resources.

Of course, this resource still doesn’t exist, so let’s create it. Right click res Directory 1 >New — >Directory to create an XML Directory, then right click XML Directory 1 >New — >File to create a file_paths.xml File. Then modify the contents of the file_paths.xml file as follows:

<? xml version="l.0" encoding="utf-8"? > <paths xmlns:android="http://schemas.android.com/apk/res/android"> 
     <external-path name="my_images" path="" />
</paths>
Copy the code

External-path is used to specify the shared Uri. The value of the name attribute is optional, and the value of the path attribute represents the specific path of the shared Uri. Set this to null to share the entire SD card, or you can just share the path where we saved output_image.jpg

  • Access to the SD card is manifestXML.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Copy the code

(1.3) Save the picture in a custom path

After the photo is taken, save the image to a custom path

mTakePhotoToCustomPath.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        File outputImage = null;
        try {
            outputImage = createImageFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(outputImage ! =null) {
            if (Build.VERSION.SDK_INT < 24) {
                mImageUri = Uri.fromFile(outputImage);
            } else {
                mImageUri = FileProvider.getUriForFile(TakePhotoActivity.this."com.lzacking.camerabasedemo.fileprovider",
                        outputImage);
            }
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            // Make sure there is a camera activity to handle intents
            if(intent.resolveActivity(getPackageManager()) ! =null) { intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); startActivityForResult(intent, TAKE_PHOTO_TO_CUSTOMPATH); }}}});Copy the code

The difference between (1.2) saving images in the associated cache directory is this custom path, and other similar content is not explained here.

private File createImageFile(a) throws IOException {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = Environment.getExternalStorageDirectory();// Get the SDCard directory
    File image = File.createTempFile(imageFileName, ".jpg", storageDir);
    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}
Copy the code

The onActivityResult() method displays the photo taken

case TAKE_PHOTO_TO_CUSTOMPATH:
    if (resultCode == RESULT_OK) {
        try {
            // Display the photos taken
            // The URI here is obtained in a different way, so there is no need for Android7.0 judgment when tailoring it.
            mPicture.setImageURI(mImageUri);
        } catch(Exception e) { e.printStackTrace(); }}break;
Copy the code

This shows the photos taken using another method, which is setImageURI ()


(1.4) Save the picture to a custom path and add it to the album

After the photo is taken, the image is saved to the custom path and added to the album. This part of the code is similar to (1.3) to save the image in the custom path. Here, the code onActivityResult() method is no longer pasted to show the photo taken

case TAKE_PHOTO_TO_ALBUM:
    if (resultCode == RESULT_OK) {
        try {
            // Display the photos taken
            mPicture.setImageURI(mImageUri);
            // Save the photo to an album, which recognizes the photo taken by the program
            addPictureToAlbum();
        } catch(Exception e) { e.printStackTrace(); }}break;
Copy the code

Add to album

private void addPictureToAlbum(a) {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File file = new File(mCurrentPhotoPath);
    try {
        MediaStore.Images.Media.insertImage(getContentResolver(),
                file.getAbsolutePath(), file.getName(), null);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }

    Uri contentUri = Uri.fromFile(file);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
    Toast.makeText(this."Add to Album success", Toast.LENGTH_SHORT).show();
}
Copy the code

The multimedia scanner can’t find our photo, because it is private to our App. The following code allows the system’s multimedia scanner to add our images to the Media Provider database, making our images available to system albums and other applications.


(1.5) The picture is zoomed

In the process of taking photos, please refer to the previous photo code and put the pictures taken on ImageView without any processing. If the picture is too large, it will cause OOM, so do a zoom processing

private void scalePicture(a) {
    // Gets the size of the view control
    int targetW = mPictureScale.getWidth();
    int targetH = mPictureScale.getHeight();
    // Get the size of the image
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine the extent to which the image is zoomed out
    int scaleFactor = Math.min(photoW / targetW, photoH / targetH);

    // Scale the image and fill it into the view control
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    mPictureScale.setImageBitmap(bitmap);
}
Copy the code

(1.6) Select a picture from the album and display it

Open the photo album

mChooseFromAlbum.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) { openAlbum(); }});Copy the code
private void openAlbum(a) {
    Intent intent = new Intent("android.intent.action.GET_CONTENT");
    intent.setType("image/*");
    startActivityForResult(intent, CHOOSE_PHOTO); // Open the album
}
Copy the code

The onActivityResult() method displays the captured photo

case CHOOSE_PHOTO:
    if (resultCode == RESULT_OK) {
        // Check the phone system version
        if (Build.VERSION.SDK_INT >= 19) {
            // Systems 4.4 and above use this method to process images
            handleImageOnKitKat(data);
        } else {
            // 4.4 The following systems use this method to process imageshandleImageBeforeKitKat(data); }}break;
Copy the code
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
    String imagePath = null;
    Uri uri = data.getData();
    Log.d("TAG"."handleImageOnKitKat: uri is " + uri);
    if (DocumentsContract.isDocumentUri(this, uri)) {
        // If it is a Uri of type document, the document ID is used
        String docId = DocumentsContract.getDocumentId(uri);
        if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
            String id = docId.split(":") [1]; // Parse out the id in numeric format
            String selection = MediaStore.Images.Media._ID + "=" + id;
            imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
        } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
            Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
            imagePath = getImagePath(contentUri, null); }}else if ("content".equalsIgnoreCase(uri.getScheme())) {
        // If it is a Uri of type Content, it is handled in the normal way
        imagePath = getImagePath(uri, null);
    } else if ("file".equalsIgnoreCase(uri.getScheme())) {
        // If it is a Uri of type file, get the image path directly
        imagePath = uri.getPath();
    }
    displayImage(imagePath); // Display the image according to the image path
}

private void handleImageBeforeKitKat(Intent data) {
    Uri uri = data.getData();
    String imagePath = getImagePath(uri, null);
    displayImage(imagePath);
}

private String getImagePath(Uri uri, String selection) {
    String path = null;
    // Use Uri and selection to get the real image path
    Cursor cursor = getContentResolver().query(uri, null, selection, null.null);
    if(cursor ! =null) {
        if (cursor.moveToFirst()) {
            path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
        }
        cursor.close();
    }
    return path;
}

private void displayImage(String imagePath) {
    if(imagePath ! =null) {
        Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
        mPicture.setImageBitmap(bitmap);
    } else {
        Toast.makeText(this."failed to get image", Toast.LENGTH_SHORT).show(); }}Copy the code

Call openAlbum () method, here we first construct an Intent object, and the action specified for android. The Intent. The action. The GET_CONTENT. Then set the required parameters for the Intent object and call startActivityForResult () to open the album application and select the photo. Notice that when we call the StartActivityForResult () method, we pass the value of the second parameter to CHOOSE_PHOTO, so that when we return to the onActivityResult () method after selecting the image from the album, I’m going to enter CHOOSE_PHOTO’s case to process the image. The next logical step is more complicated. First, in order to be compatible with older and newer phones, we make a decision that if the phone is 4.4 or above, we call handlelmageOnKitKat (), otherwise we call handlelmageBeforeKitKat(). The reason for this is that starting with version 4.4, Android does not return the actual Uri of an image in an album. Instead, it returns an encapsulated Uri, so phones later than version 4.4 need to parse this Uri.

The logic in the handlelmageOnKitKat () method is basically how to parse the encapsulated Uri. There are several ways to determine the situation: if the returned Uri is of type Document, fetch the document ID and handle it; if not, handle it the normal way. In addition, if the Uri’s authority is in media format, the document ID needs to be parsed again, and the second half of the document ID needs to be extracted by string splitting to get the actual numeric ID. The extracted ID is used to construct a new Uri and condition statement, and these values are passed as arguments to the getlmagePath () method to get the actual path to the image. Once you get the path to the image, call the DisplayImage0 method to display the image on the screen.

Compared to the handlelmageOnKitKat () method, the logic in the handlelmageBeforeKitKat () method is much simpler because its Uri is unencapsulated and does not require any parsing, Pass the Uri directly to the getlmagePath () method to get the actual path of the image, and finally call the displayLmage () method to display the image on the screen.


(2) Video recording API introduction

(2.1) Record video and display

You build an Intent object, specify its action as mediastore.action_video_capture, and then call startActivityForResult () to start recording the video

mTakeVideo.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        if(intent.resolveActivity(getPackageManager()) ! =null) { startActivityForResult(intent, TAKE_VIDEO); }}});Copy the code

Play the recorded video

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    switch (requestCode) {
        case TAKE_VIDEO:
            if (resultCode == RESULT_OK) {
                Uri videoUri = intent.getData();
                mVideoView.setVideoURI(videoUri);
                mVideoView.requestFocus();
                mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mp) {
                        mp.setLooping(true);// Set the video to repeat}}); mVideoView.start();// Play the video
                MediaController mediaController = new MediaController(this);// Display the control bar
                mVideoView.setMediaController(mediaController);
                mediaController.setMediaPlayer(mVideoView);// Set the object to control
                mediaController.show();
            }
            break;
        default:
            break; }}Copy the code

(3) source code address

Android audio and Video development foundation (7) : Video capture – system API foundation