[Android多媒體二]調用系統錄音機錄音並存儲到指定位置,適配安卓 7.0

接上一篇文章,[Android多媒體一]調用系統相機拍照並存儲到指定位置,適配安卓 7.0

本文講述如何調用系統錄音機,完成錄音後,對錄音進行指定位置的保存。

一、開始編寫

首先,還是理清一下思路,在着手編寫代碼。

1、啓動系統錄音機並保存到指定位置依然設計讀寫權限,此時需要向用戶請求權限,並根據用戶操作進行相應的動作。錄音使用到的權限有:

    <!-- 讀寫權限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- 錄音權限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

2、將錄音文件保存到SD的指定位置,需要創建一定的目錄層級,像上一篇文章講述的一樣,這次,把錄音文件保存爲SD根目錄下的TestDir/voice/xxx.amr。

3、根據用戶錄音的結果,進行存儲操作。

4、不同於啓動相機拍照,這次,不把uri加入到啓動錄音機的額外數據,因爲不管加還是不加,錄音成功後,獲取到的uri都是系統存放剛剛的錄音文件的uri。

廢話不多說,直接上代碼:

主界面佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="拍照並保存" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="錄音並保存" />

</LinearLayout>

MainActivity代碼:

package com.my.example.multimediatest;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    public static final String SD_APP_DIR_NAME = "TestDir"; //存儲程序在外部SD卡上的根目錄的名字
    public static final String PHOTO_DIR_NAME = "photo";    //存儲照片在根目錄下的文件夾名字
    public static final String VOICE_DIR_NAME = "voice";    //存儲音頻在根目錄下的文件夾名字
    public static final String VIDEO_DIR_NAME = "video";    //存儲視頻在根目錄下的文件夾名字

    public static final int PHOTO_RESULT_CODE = 100;        //標誌符,圖片的結果碼,判斷是哪一個Intent
    public static final int VOICE_RESULT_CODE = 101;        //標誌符,音頻的結果碼,判斷是哪一個Intent
    public static final int VIDEO_RESULT_CODE = 102;        //標誌符,視頻的結果碼,判斷是哪一個Intent

    private String mImagePath;             //用於存儲圖片的最終目錄,即根目錄 / 圖片的文件夾 / 圖片
    private Uri mImageUri;                 //存儲相機返回的uri
    private String mImageName;             //保存的圖片的名字
    private File mImageFile;               //圖片文件

    private String mVoicePath;             //用於存儲錄音的最終目錄,即根目錄 / 錄音的文件夾 / 錄音
    private String mVoiceName;             //保存的錄音的名字
    private File mVoiceFile;               //錄音文件


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "開始...");

        // android 7.0系統解決拍照的問題
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());
        builder.detectFileUriExposure();

        //拍照按鈕的點擊事件
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]
                        {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 200);
            }
        });

        //錄音按鈕的點擊事件
        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]
                        {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 201);
            }
        });
    }

    /**
     * 返回用戶是否允許權限的結果,並處理
     */
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResult) {
        if (requestCode == 200) {
            //用戶允許權限
            if (grantResult[0] == PackageManager.PERMISSION_GRANTED && grantResult[1] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "用戶已允許權限,準備啓動相機。");
                //啓動照相機
                startCamera();
            } else {  //用戶拒絕
                Log.d(TAG, "用戶已拒絕權限,程序終止。");
                Toast.makeText(this, "程序需要足夠權限才能運行", Toast.LENGTH_SHORT).show();
            }
        }
        if (requestCode == 201) {
            //用戶允許權限
            if (grantResult[0] == PackageManager.PERMISSION_GRANTED && grantResult[1] == PackageManager.PERMISSION_GRANTED) {
                //啓動錄音機
                startRecord();
            } else {
                Log.d(TAG, "用戶已拒絕權限,程序終止。");
                Toast.makeText(this, "程序需要足夠權限才能運行", Toast.LENGTH_SHORT).show();
            }
        }
    }

    /**
     * 啓動錄音機,創建文件
     */
    private void startRecord() {

        Intent intent = new Intent();
        intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
        createVoiceFile();
        Log.d(TAG, "創建錄音文件");
        //添加權限
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Log.d(TAG, "啓動系統錄音機,開始錄音...");
        startActivityForResult(intent, VOICE_RESULT_CODE);
    }

    /**
     * 創建音頻目錄
     */
    private void createVoiceFile() {
        mVoiceName = getMyTime() + ".amr";
        Log.d(TAG, "錄音文件名稱:" + mVoiceName);
        mVoiceFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                + "/" + SD_APP_DIR_NAME + "/" + VOICE_DIR_NAME + "/", mVoiceName);
        mVoicePath = mVoiceFile.getAbsolutePath();
        mVoiceFile.getParentFile().mkdirs();
        Log.d(TAG, "按設置的目錄層級創建音頻文件,路徑:" + mVoicePath);
        mVoiceFile.setWritable(true);
    }

    /**
     * 啓動相機,創建文件,並要求返回uri
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void startCamera() {
        Intent intent = new Intent();
        //指定動作,啓動相機
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        Log.d(TAG, "指定啓動相機動作,完成。");
        //創建文件
        createImageFile();
        Log.d(TAG, "創建圖片文件結束。");
        //添加權限
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Log.d(TAG, "添加權限。");
        //獲取uri
        mImageUri = FileProvider.getUriForFile(this, "com.my.example.multimediatest.provider", mImageFile);
        Log.d(TAG, "根據圖片文件路徑獲取uri。");
        //將uri加入到額外數據
        intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
        Log.d(TAG, "將uri加入啓動相機的額外數據。");
        Log.d(TAG, "啓動相機...");
        //啓動相機並要求返回結果
        startActivityForResult(intent, PHOTO_RESULT_CODE);
        Log.d(TAG, "拍攝中...");
    }

    /**
     * 創建圖片文件
     */
    private void createImageFile() {
        Log.d(TAG, "開始創建圖片文件...");
        //設置圖片文件名(含後綴),以當前時間的毫秒值爲名稱
        mImageName = getMyTime() + ".jpg";
        Log.d(TAG, "設置圖片文件的名稱爲:" + mImageName);
        //創建圖片文件
        mImageFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                + "/" + SD_APP_DIR_NAME + "/" + PHOTO_DIR_NAME + "/", mImageName);
        //將圖片的絕對路徑設置給mImagePath,後面會用到
        mImagePath = mImageFile.getAbsolutePath();
        //按設置好的目錄層級創建
        mImageFile.getParentFile().mkdirs();
        Log.d(TAG, "按設置的目錄層級創建圖片文件,路徑:" + mImagePath);
        //不加這句會報Read-only警告。且無法寫入SD
        mImageFile.setWritable(true);
        Log.d(TAG, "將圖片文件設置可寫。");
    }

    /**
     * 處理返回結果。
     * 1、圖片
     * 2、音頻
     * 3、視頻
     *
     * @param requestCode 請求碼
     * @param resultCode  結果碼 成功 -1 失敗 0
     * @param data        返回的數據
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        super.onActivityResult(requestCode, resultCode, data);

//        Log.d(TAG, "拍攝結束。");
        Log.d(TAG, "錄音結束。");
        if (resultCode == Activity.RESULT_OK) {
            Log.d(TAG, "返回成功。");
            Log.d(TAG, "請求碼:" + requestCode + "  結果碼:" + resultCode + "  data:" + data);
            switch (requestCode) {
                case PHOTO_RESULT_CODE: {
                    Bitmap bitmap = null;
                    try {
                        //根據uri設置bitmap
                        bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), mImageUri);
                        Log.d(TAG, "根據uri設置bitmap。");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //將圖片保存到SD的指定位置
                    savePhotoToSD(bitmap);
                    //更新系統圖庫
                    updateSystemGallery();
                    Log.d(TAG, "結束。");
                    break;
                }
                case VOICE_RESULT_CODE: {
                    try {
                        Uri uri = data.getData();
                        String filePath = getAudioFilePathFromUri(uri);
                        Log.d(TAG, "根據uri獲取文件路徑:" + filePath);
                        Log.d(TAG, "開始保存錄音文件");
                        saveVoiceToSD(filePath);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }

                    break;
                }
                case VIDEO_RESULT_CODE: {
//                    saveVideoTOSD();
                    break;
                }
            }
        }
    }

    /**
     * 保存照片到SD卡的指定位置
     */
    private void savePhotoToSD(Bitmap bitmap) {
        Log.d(TAG, "將圖片保存到指定位置。");
        //創建輸出流緩衝區
        BufferedOutputStream os = null;
        try {
            //設置輸出流
            os = new BufferedOutputStream(new FileOutputStream(mImageFile));
            Log.d(TAG, "設置輸出流。");
            //壓縮圖片,100表示不壓縮
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
            Log.d(TAG, "保存照片完成。");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    //不管是否出現異常,都要關閉流
                    os.flush();
                    os.close();
                    Log.d(TAG, "刷新、關閉流");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 更新系統圖庫
     */
    private void updateSystemGallery() {
        //把文件插入到系統圖庫
        try {
            MediaStore.Images.Media.insertImage(this.getContentResolver(),
                    mImageFile.getAbsolutePath(), mImageName, null);
            Log.d(TAG, "將圖片文件插入系統圖庫。");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 最後通知圖庫更新
        this.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + mImagePath)));
        Log.d(TAG, "通知系統圖庫更新。");
    }

    /**
     * 獲取日期並格式化
     * 如:2017_10_20 週三 上午 11:20:35
     *
     * @return 格式化好的日期字符串
     */
    private String getMyTime() {
        //存儲格式化後的時間
        String time;
        //存儲上午下午
        String ampTime = "";
        //判斷上午下午,am上午,值爲 0 ; pm下午,值爲 1
        int apm = Calendar.getInstance().get(Calendar.AM_PM);
        if (apm == 0) {
            ampTime = "上午";
        } else {
            ampTime = "下午";
        }
        //設置格式化格式
        SimpleDateFormat format = new SimpleDateFormat("yyyy_MM_dd E " + ampTime + " kk:mm:ss");
        time = format.format(new Date());

        return time;
    }

    /**
     * 保存音頻到SD卡的指定位置
     *
     * @param path 錄音文件的路徑
     */
    private void saveVoiceToSD(String path) {
        //創建輸入輸出
        InputStream isFrom = null;
        OutputStream osTo = null;
        try {
            //設置輸入輸出流
            isFrom = new FileInputStream(path);
            osTo = new FileOutputStream(mVoicePath);
            byte bt[] = new byte[1024];
            int len;
            while ((len = isFrom.read(bt)) != -1) {
                Log.d(TAG, "len = " + len);
                osTo.write(bt, 0, len);
            }
            Log.d(TAG, "保存錄音完成。");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (osTo != null) {
                try {
                    //不管是否出現異常,都要關閉流
                    osTo.close();
                    Log.d(TAG, "關閉輸出流");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (isFrom != null) {
                try {
                    isFrom.close();
                    Log.d(TAG, "關閉輸入流");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 保存視頻到SD卡的指定位置
     */
    private void saveVideoTOSD() {

    }

    /**
     * 通過Uri,獲取錄音文件的路徑(絕對路徑)
     *
     * @param uri 錄音文件的uri
     * @return 錄音文件的路徑(String)
     */
    private String getAudioFilePathFromUri(Uri uri) {
        Cursor cursor = getContentResolver()
                .query(uri, null, null, null, null);
        cursor.moveToFirst();
        int index = cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA);
        String temp = cursor.getString(index);
        cursor.close();
        return temp;
    }

}

實現思路和拍攝圖片並保存的思路一致,都是在啓動系統程序前檢查有無讀寫權限,有權限則創建對應的目錄層級和對應的文件,操作完成後通過返回的結果進行保存操作。

二、運行結果

還是完整的執行流程。

接下來去系統的文件夾下找到剛剛錄音的文件。


可以看到保存的位置、文件名稱都符合預期。並且可以播放,文件大小由於是amr格式,所以非常小。

實現啓動錄音機錄音,並保存到指定位置還是非常簡單的,代碼量相對於相機來說少了很多。邏輯也更清晰了。


相關文章:[Android多媒體一]調用系統相機拍照並存儲到指定位置,適配安卓 7.0


原創文章,轉載請註明出處:https://blog.csdn.net/Lone1yCode/article/details/79951477


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章