当前位置:   article > 正文

编写一个录音软件,使用AudioRecorder录制音频,并使用MediaCodeC编码(aac格式),编码封装成mp4文件,保存到对应目录_mediacodec 保存mp4

mediacodec 保存mp4

使用AudioRecorder + MediaCodeC,从0到1构建一个录音软件

根据所编写的流程,一步一步来,理解所写的内容,一定会有不小的收获

一、架构给的大致流程(针对于自主研发的车机)

  1. 点击开始录制
  2. 点击结束录制(prepare shutdown车辆准备下电时也要结束录制)
  3. recordApp通过audioRecorder进行录制,建议参数16khz,8channel,16bit
  4. 录到原始的pcm数据后,需要根据车型选择通道,具体根据产品定义(如左前、右后)
  5. 使用MediaCodeC进行编码,建议(aac格式)
  6. 使用MediaMuxer进行容器封装,保存音频文件,文件大小限制30M,满后结束,并重新开始新文件录制,文件命名:AudioFile_vin号(车架号,车辆的唯一标识)_年月日_开始时分秒_结束时分秒
  7. 将最终文件存放到android对应目录下,由logService进行文件上传

二、我的编码思路

  1. 创建项目
  2. 修改配置文件,依赖公司的maven的仓库
  3. 申请audio录制、文件读写相关的权限
  4. 初始化一个服务,录制相关的操作放到servcie中去做
  5. 检查文件最终存放的目录是否存在,如果不存在,就新创建一个
  6. 检查配置麦克风相关内容
  7. 配置AudioRecorder参数,初始化AudioRecorder
  8. 开始录音,新建一个线程去执行耗时操作,将音频数据通过io流放入到原始pcm文件中
  9. 结束录音,释放相关的资源,根据车机环境以及录制的时间点等等构建最终的mp4文件名称
  10. 原始的pcm文件太大,需要对其进行裁剪拼接处理(因为选择的是8通道,录到的是车机所有的声音,而vc1我们需要的是主驾的,即左前的数据)
  11. 使用MediaCodeC对pcm文件进行编码,添加ADTS头,对文件进行容器封装生成最终的mp4文件

三、具体的编码实践

先看一下最终的目录结构,以及最终的实现效果
在这里插入图片描述
在这里插入图片描述

  1. 配置公司对应车型的仓库依赖。配置项目包名、签名、依赖
    在setting.gradle添加对应maven依赖
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        //添加公司的maven仓库依赖
        maven {
            allowInsecureProtocol = true
            url 'http://nexus3.human-horizons.com:8081/repository/mce-release'
        }
    }
}
rootProject.name = "SoundRecorder"
include ':app'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在app目录下的build.gradle中配置:包名、签名、dataBinding、项目所需的依赖等等

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.hryt.soundrecorder'
    compileSdk 33

    defaultConfig {
        applicationId "com.hryt.soundrecorder"
        minSdk 30
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
        debug {
            storeFile file('hryt.jks')
            storePassword 'human-horizons'
            keyAlias = 'hryt'
            keyPassword 'human-horizons'
        }
        release {
            storeFile file('hryt.jks')
            storePassword 'human-horizons'
            keyAlias = 'hryt'
            keyPassword 'human-horizons'
        }
    }

    dataBinding {
        enabled = true
    }

    buildTypes {
        debug {
            signingConfig signingConfigs.debug
            debuggable true
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    implementation 'com.hryt.UIFramework:design3:+'
    implementation "com.hryt.app.platform:AppCommon-Logger:+"
    implementation "com.hryt.opensdk:com.hht.audio:+"
    implementation 'io.reactivex.rxjava3:rxjava:3.0.13'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    implementation "com.hryt.app.platform:AppMW-ConfigRepo:+"
    implementation "com.hryt.app.platform:AppMW-CarPowerRepo:+"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  1. 申请权限
    app目录下的AndroidManifest.xml中申明使用权限
	<uses-permission android:name="android.permission.RECORD_AUDIO" />
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
	<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
  • 1
  • 2
  • 3
  • 4

在MainActivity中检查权限,申请权限

	private static final String[] PERMISSIONS = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.MANAGE_EXTERNAL_STORAGE};
                @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        Logger.tag(TAG).i(" onCreate ");
        super.onCreate(savedInstanceState);
        checkPermissionAndRequest();
    }
    private void checkPermissionAndRequest() {
        for (int i = 0; i < PERMISSIONS.length; i++) {
            if (ContextCompat.checkSelfPermission(this, PERMISSIONS[i]) != PackageManager.PERMISSION_GRANTED) {
                Logger.tag(TAG).i(" checkPermissionAndRequest not have permission = " + PERMISSIONS[i]);
                ActivityCompat.requestPermissions(this, new String[]{PERMISSIONS[i]}, i);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  1. 创建服务,用于执行业务操作,使用接口来通信
    在AndroidManifest.xml中,service属于四大组件之一,都需要在这个文件中声明
<application
        android:name=".SoundRecorderApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/HrytTheme"
        tools:targetApi="31">

        <service
            android:name=".service.SoundRecorderService"
            android:enabled="true"
            android:exported="false" />

        <activity
            android:name=".view.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

定义开始录制以及结束录制的接口

public interface ISoundRecord {
    void startRecord();

    void stopRecord();
}
  • 1
  • 2
  • 3
  • 4
  • 5

在service中定义私有内部类实现这个接口

public class SoundRecorderService extends BaseService {
    private static final String TAG = SoundRecorderService.class.getSimpleName();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Logger.tag(TAG).i(" onBind ");
        return new SoundRecordBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Logger.tag(TAG).i(" onUnbind ");
        return super.onUnbind(intent);
    }
	//具体的业务代码先不粘贴,免得代码太多影响思路
    private class SoundRecordBinder extends Binder implements ISoundRecord {
    		@Override
        public void startRecord() {
        //业务代码
        }
				@Override
        public void stopRecord() {
        //业务代码
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在viewModel中绑定这个服务,绑定成功后,就可以使用这个接口去调用service中业务代码实现了

public class MainViewModel extends BaseViewModel {
	private static final String TAG = MainViewModel.class.getSimpleName();
	private static Context context = AppGlobals.getInitialApplication().getApplicationContext();
	public MutableLiveData<Boolean> mIsRecord = new MutableLiveData<>(false);
    private ISoundRecord mISoundRecord;
    
    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Logger.tag(TAG).i(" onServiceConnected: " + service);
            mISoundRecord = (ISoundRecord) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Logger.tag(TAG).i(" onServiceDisconnected ");
            mISoundRecord = null;
        }
    };
    
	public void initService() {
        Logger.tag(TAG).i(" initService ");
        Intent intent = new Intent(context, SoundRecorderService.class);
        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    public void unbindService() {
        Logger.tag(TAG).i(" unbindService ");
        context.unbindService(connection);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  1. 检查目录是否存在,初始化carPower,初始化listener,初始化Observer
    MainActivity大体全部代码
public class MainActivity extends BaseBVMActivity<ActivityMainBinding, MainViewModel> {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static final String[] PERMISSIONS = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.MANAGE_EXTERNAL_STORAGE};

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    public void onOverlayChanged(@NonNull Configuration newConfig) {
        super.onOverlayChanged(newConfig);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        Logger.tag(TAG).i(" onCreate ");
        super.onCreate(savedInstanceState);
        checkPermissionAndRequest();
    }

    private void checkPermissionAndRequest() {
        for (int i = 0; i < PERMISSIONS.length; i++) {
            if (ContextCompat.checkSelfPermission(this, PERMISSIONS[i]) != PackageManager.PERMISSION_GRANTED) {
                Logger.tag(TAG).i(" checkPermissionAndRequest not have permission = " + PERMISSIONS[i]);
                ActivityCompat.requestPermissions(this, new String[]{PERMISSIONS[i]}, i);
            }
        }
    }

    @Override
    public void onResume() {
        Logger.tag(TAG).i(" onResume ");
        super.onResume();
    }

    @Override
    protected void initData() {
        Logger.tag(TAG).i(" init data ");
        binding.setVm(viewModel);
        initService();
        checkDirExistence();
        initListener();
        initCarPower();
        initObserver();
    }

    private void initCarPower() {
        Logger.tag(TAG).i(" init car power ");
        viewModel.initCarPower();
    }

    private void initService() {
        Logger.tag(TAG).i(" initService ");
        viewModel.initService();
    }

    private void checkDirExistence() {
        viewModel.checkDirExistence();
    }

    private void initListener() {
        Logger.tag(TAG).i(" initListener ");
        binding.clStartAndStopRecord.setOnClickListener(v -> {
            if (viewModel.mIsRecord.getValue()) {
                viewModel.stopRecord();
            } else {
                viewModel.startRecord();
                MainActivity.this.moveTaskToBack(false);
            }
        });
    }

    private void initObserver() {
        viewModel.mIsRecord.observe(this, aBoolean -> {
            Logger.tag(TAG).i(" initObserver mIsRecord = " + aBoolean);
        });
    }

    @Override
    public void onDestroy() {
        Logger.tag(TAG).i(" onDestroy ");
        viewModel.unbindService();
        super.onDestroy();
    }

    @Override
    public void onPause() {
        Logger.tag(TAG).i(" onPause ");
        super.onPause();
    }

    @Override
    public void onStop() {
        Logger.tag(TAG).i(" onStop ");
        super.onStop();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

MainViewModel大体全部代码

public class MainViewModel extends BaseViewModel {
    private static final String TAG = MainViewModel.class.getSimpleName();

    public MutableLiveData<Boolean> mIsRecord = new MutableLiveData<>(false);
    private static Context context = AppGlobals.getInitialApplication().getApplicationContext();
    private ISoundRecord mISoundRecord;

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Logger.tag(TAG).i(" onServiceConnected: " + service);
            mISoundRecord = (ISoundRecord) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Logger.tag(TAG).i(" onServiceDisconnected ");
            mISoundRecord = null;
        }
    };

    public void initService() {
        Logger.tag(TAG).i(" initService ");
        Intent intent = new Intent(context, SoundRecorderService.class);
        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    public void unbindService() {
        Logger.tag(TAG).i(" unbindService ");
        context.unbindService(connection);
    }

    public void startRecord() {
        if (mISoundRecord != null) {
            mISoundRecord.startRecord();
            mIsRecord.setValue(true);
        } else {
            Logger.tag(TAG).i("startRecord not bind, retry one time");
            initService();
        }
    }

    public void stopRecord() {
        if (mISoundRecord != null) {
            mISoundRecord.stopRecord();
            mIsRecord.setValue(false);
        } else {
            Logger.tag(TAG).i("stopRecord not bind, retry one time");
            initService();
        }
    }

    public void checkDirExistence() {
        Logger.tag(TAG).i(" checkDirExistence ");
        SoundRecordFileUtil.createNewDir(Constants.BASE_PATH + Constants.SOUND_RECORDER);
    }

    public void initCarPower() {
        CarPowerRepository.getInstance(AppGlobals.getInitialApplication())
                .registerCarPowerCallBack(new ICarPowerCallBack() {
            @Override
            public void onCarPowerSuspend() {

            }

            @Override
            public void onCarPowerPrepareShutDown() {
                Logger.tag(TAG).i(" onCarPowerPrepareShutDown ");
                ICarPowerCallBack.super.onCarPowerPrepareShutDown();
                if (Boolean.TRUE.equals(mIsRecord.getValue())) {
                    stopRecord();
                }
            }
        });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

checkDirExistence对应的工具类(避免影响思路,先展示一部分)

public class SoundRecordFileUtil {
	    public static void createNewDir(String dirPath) {
        File dirFile = new File(dirPath);
        if (!dirFile.exists()) {
            dirFile.mkdir();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 开始录制
    SoundRecorderService.SoundRecordBinder中的业务代码
private class SoundRecordBinder extends Binder implements ISoundRecord {
        private final String audioNamePrefix = "AudioFile";
        private volatile boolean isRecording;
        private volatile File currentPcmFile;
        private volatile long startTime;

        @Override
        public void startRecord() {
            if (isRecording) {
                Logger.tag(TAG).i(" startRecord is recording return");
                return;
            }
            AudioFocusManager.getInstance().setMicMute(true);
            AudioRecord audioRecord = AudioRecordManager.getInstance().getAudioRecord();
            int recordBuffSize = AudioRecordManager.getInstance().getRecordBuffSize();
            isRecording = true;
            audioRecord.startRecording();
            Logger.tag(TAG).i(" startRecord start recording");
            RxUtils.handleRecord(Completable.create(emitter -> {
                startRecording(audioRecord, recordBuffSize);
            }));
        }

        private void startRecording(AudioRecord audioRecord, int recordBuffSize) {
            Logger.tag(TAG).i(" startRecording begin recordBuffSize = " + recordBuffSize);
            byte[] data = new byte[recordBuffSize];
            startTime = System.currentTimeMillis();
            currentPcmFile = new File(Constants.BASE_PATH + startTime + ".pcm");

            FileOutputStream fileOutputStream = null;
            try {
                if (!currentPcmFile.exists()) {
                    currentPcmFile.createNewFile();
                    Logger.tag(TAG).i(" create a file name = " + currentPcmFile.getPath());
                }
                fileOutputStream = new FileOutputStream(currentPcmFile);
            } catch (IOException e) {
                Logger.tag(TAG).i(" create new file " + e.getMessage());
            }
            int read;
            if (fileOutputStream != null) {
                while (isRecording) {
                    read = audioRecord.read(data, 0, recordBuffSize);
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        try {
                            fileOutputStream.write(data);
                        } catch (IOException e) {
                            Logger.tag(TAG).i("write date " + e.getMessage());
                        }
                    }
                }
            }
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                Logger.tag(TAG).i(e.getMessage());
            }
        }

        @Override
        public void stopRecord() {
            Logger.tag(TAG).i(" stopRecord ");
            isRecording = false;
            AudioFocusManager.getInstance().setMicMute(false);
            AudioRecordManager.getInstance().releaseAudioRecord();
            Logger.tag(TAG).i(" init file time ");
            Long endTime = System.currentTimeMillis();
            //将pcm文件通过aac编码转换为mp4文件
            StringBuilder mp4FileNameBuilder = new StringBuilder();
            //获取车机vin号
            String vin = AppMWConfigRepository.getInstance(AppGlobals.getInitialApplication()).getVin();
            String day = RecordDateUtil.millisToString(startTime, RecordDateUtil.YEAR_MONTH_DAY);
            String startT = RecordDateUtil.millisToString(startTime, RecordDateUtil.HOUR_MINUTE_SECOND);
            String endT = RecordDateUtil.millisToString(endTime, RecordDateUtil.HOUR_MINUTE_SECOND);
            mp4FileNameBuilder.append(Constants.BASE_PATH).append(Constants.SOUND_RECORDER).append(File.separator)
                    .append(audioNamePrefix).append("_").append(vin).append("_").append(day).append("_")
                    .append(startT).append("_").append(endT).append(".mp4");
            Logger.tag(TAG).i("create mp4 file name = " + mp4FileNameBuilder);
            //创建转换的实例
            PcmToMp4Util pcmToMp4 = new PcmToMp4Util(currentPcmFile, mp4FileNameBuilder.toString());
            RxUtils.handleRecord(Completable.create(emitter -> {
                Logger.tag(TAG).i(" start original pcm data convert to byte array");
                //文件太大,直接将原始的pcm数据转换成字节数组,会导致内存溢出,所以需要将大文件先切割,再转换
                List<File> fileList = SoundRecordFileUtil.splitFile(currentPcmFile,
                        Constants.BASE_PATH, Constants.SMALL_FILE_SIZE);

                Logger.tag(TAG).i(" clear original data ");
                //将原始的pcm文件的数据清掉
                SoundRecordFileUtil.clearFileContent(currentPcmFile);

                Logger.tag(TAG).i(" add file list date to pcm file ");
                //遍历小文件列表,将数据加入到pcm文件中
                SoundRecordFileUtil.addDataToPcm(currentPcmFile, fileList);

                Logger.tag(TAG).i(" delete files ");
                //将原来的小文件们都删除
                SoundRecordFileUtil.deleteFiles(fileList);

                Logger.tag(TAG).i(" pcm conversion to mp4 ");
                //将pcm文件转换为mp4文件
                startPcmToMp4(pcmToMp4);

                Logger.tag(TAG).i(" delete original pcm file ");
                //最后将pcm文件删除
                SoundRecordFileUtil.deleteFile(currentPcmFile);
            }));
        }

        private void startPcmToMp4(PcmToMp4Util pcmToMp4) {
            pcmToMp4.startPcmToMp4();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

判断当前是否在录制中,如果在录制中直接return,检查麦克风

	if (isRecording) {
    	Logger.tag(TAG).i(" startRecord is recording return");
       	return;
    }
    AudioFocusManager.getInstance().setMicMute(true);
  • 1
  • 2
  • 3
  • 4
  • 5

AudioFocusManager单例类,音频以及麦克风

public class AudioFocusManager {
    private static final String TAG = AudioFocusManager.class.getSimpleName();
    private Context mContext;
    private AudioManager mAudioManager;
    private static volatile AudioFocusManager mInstance;
    private AudioFocusManager() {

    }

    public static AudioFocusManager getInstance() {
        if (mInstance == null) {
            synchronized (AudioFocusManager.class) {
                if (mInstance == null) {
                    mInstance = new AudioFocusManager();
                }
            }
        }
        return mInstance;
    }

    public void init(Context context) {
        this.mContext = context;
        initAudio();
    }

    private void initAudio() {
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        Logger.tag(TAG).i(" initAudio microphoneMute = " + isMicrophoneMute());
        if (isMicrophoneMute()) {
            mAudioManager.setMicrophoneMute(false);
        }
    }

    public void release() {
        Logger.tag(TAG).i(" release isMicrophoneMute = " + isMicrophoneMute());
        if (isMicrophoneMute()) {
            mAudioManager.setMicrophoneMute(false);
        }
    }

    public void setMicMute(boolean micMute) {
        Logger.tag(TAG).i(" setMicMute value = " + micMute);
        mAudioManager.setMicrophoneMute(micMute);
    }

    public boolean isMicrophoneMute() {
        Logger.tag(TAG).i(" isMicrophoneMute value = " + mAudioManager.isMicrophoneMute());
        return mAudioManager.isMicrophoneMute();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

构建AudioRecorder实例,进行录制

	AudioRecord audioRecord = AudioRecordManager.getInstance().getAudioRecord();
	int recordBuffSize = AudioRecordManager.getInstance().getRecordBuffSize();
	isRecording = true;
	audioRecord.startRecording();
	Logger.tag(TAG).i(" startRecord start recording");
  • 1
  • 2
  • 3
  • 4
  • 5

AudioRecordManager单例类,构建音频录制类,以及音频缓冲大小

public class AudioRecordManager {
    private static final String TAG = AudioRecordManager.class.getSimpleName();
    private AudioRecord mAudioRecord;
    private int recordBuffSize;
    private static volatile AudioRecordManager mInstance;

    private AudioRecordManager() {

    }

    public static AudioRecordManager getInstance() {
        if (mInstance == null) {
            synchronized (AudioRecordManager.class) {
                if (mInstance == null) {
                    mInstance = new AudioRecordManager();
                }
            }
        }
        return mInstance;
    }

    private AudioRecord initAudioRecord() {
        recordBuffSize = AudioRecord.getMinBufferSize(Constants.CONTENT_SAMPLING_RATE,
                Constants.CHANNEL_IN_EIGHT, AudioFormat.ENCODING_PCM_16BIT);
        Logger.tag(TAG).i(" initAudioRecord recordBuffSize = " + recordBuffSize);
        if (ActivityCompat.checkSelfPermission(AppGlobals.getInitialApplication(),
                Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            Logger.tag(TAG).i(" not have permission = " + Manifest.permission.RECORD_AUDIO);
            return null;
        }
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                Constants.CONTENT_SAMPLING_RATE, Constants.CHANNEL_IN_EIGHT,
                AudioFormat.ENCODING_PCM_16BIT, recordBuffSize);
        return mAudioRecord;
    }

    public AudioRecord getAudioRecord() {
        Logger.tag(TAG).i(" getAudioRecord mAudioRecord = " + mAudioRecord);
        if (mAudioRecord == null) {
            mAudioRecord = initAudioRecord();
        }
        return mAudioRecord;
    }

    public int getRecordBuffSize() {
        Logger.tag(TAG).i(" getRecordBuffSize recordBuffSize = " + recordBuffSize);
        return recordBuffSize;
    }

    public void releaseAudioRecord() {
        Logger.tag(TAG).i(" releaseAudioRecord mAudioRecord = " + mAudioRecord);
        if (mAudioRecord != null) {
            mAudioRecord.stop();
            mAudioRecord.release();
            mAudioRecord = null;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

定义相关常量

public class Constants {
    public static final String BASE_PATH = Environment
            .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + File.separator;
    public static final String SOUND_RECORDER = "soundrecorder";
    //内容的采样率
    public static final int CONTENT_SAMPLING_RATE = 44100;
    //比特率
    public static final int CONTENT_BIT_RATE = 64000;
    //缓冲区大小
    public static final int BUFFER_SIZE = 65536;
    //要求的最低sdk版本
    public static final int MIN_SDK_INT = 21;
    //八通道
    public static final int CHANNEL_IN_EIGHT = AudioFormat.CHANNEL_IN_LEFT
            | AudioFormat.CHANNEL_IN_RIGHT
            | AudioFormat.CHANNEL_IN_FRONT
            | AudioFormat.CHANNEL_IN_BACK
            | AudioFormat.CHANNEL_IN_LEFT_PROCESSED
            | AudioFormat.CHANNEL_IN_RIGHT_PROCESSED
            | AudioFormat.CHANNEL_IN_FRONT_PROCESSED
            | AudioFormat.CHANNEL_IN_BACK_PROCESSED;

    public static final int BYTE_ARRAY_SIZE = 1024;
    public static final int PCM_SPLIT_STEP = 16;
    public static final int SMALL_FILE_SIZE = 16777216;

    //numbers
    public static final int INT_ZERO = 0;
    public static final int INT_ONE = 1;
    public static final int INT_TWO = 2;
    public static final int INT_THREE = 3;
    public static final int INT_FOUR = 4;
    public static final int INT_FIVE = 5;
    public static final int INT_SIX = 6;
    public static final int INT_SEVEN = 7;
    public static final int INT_EIGHT = 8;
    public static final int INT_NINE = 9;
    public static final int INT_TEN = 10;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

新开线程进行字节流写入

	RxUtils.handleRecord(Completable.create(emitter -> {
        startRecording(audioRecord, recordBuffSize);
    }));
	private void startRecording(AudioRecord audioRecord, int recordBuffSize) {
    	Logger.tag(TAG).i(" startRecording begin recordBuffSize = " + recordBuffSize);
        	byte[] data = new byte[recordBuffSize];
            startTime = System.currentTimeMillis();
            currentPcmFile = new File(Constants.BASE_PATH + startTime + ".pcm");

            FileOutputStream fileOutputStream = null;
            try {
                if (!currentPcmFile.exists()) {
                    currentPcmFile.createNewFile();
                    Logger.tag(TAG).i(" create a file name = " + currentPcmFile.getPath());
                }
                fileOutputStream = new FileOutputStream(currentPcmFile);
            } catch (IOException e) {
                Logger.tag(TAG).i(" create new file " + e.getMessage());
            }
            int read;
            if (fileOutputStream != null) {
                while (isRecording) {
                    read = audioRecord.read(data, 0, recordBuffSize);
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        try {
                            fileOutputStream.write(data);
                        } catch (IOException e) {
                            Logger.tag(TAG).i("write date " + e.getMessage());
                        }
                    }
                }
            }
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                Logger.tag(TAG).i(e.getMessage());
            }    
    }            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

rxjava线程工具类

public class RxUtils {
    private static final String TAG = RxUtils.class.getSimpleName();

    public static void handleRecord(Completable completable) {
        Logger.tag(TAG).i(" handleRecord ");
        completable.subscribeOn(Schedulers.io()).subscribe();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 结束录制

结束录制,释放资源

	Logger.tag(TAG).i(" stopRecord ");
    isRecording = false;
    AudioFocusManager.getInstance().setMicMute(false)
    AudioRecordManager.getInstance().releaseAudioRecord();
  • 1
  • 2
  • 3
  • 4

创建新文件名称

	Logger.tag(TAG).i(" init file time ");
    Long endTime = System.currentTimeMillis();
   	//将pcm文件通过aac编码转换为mp4文件
    StringBuilder mp4FileNameBuilder = new StringBuilder();
    //获取车机vin号
    String vin = AppMWConfigRepository.getInstance(AppGlobals.getInitialApplication()).getVin();
    String day = RecordDateUtil.millisToString(startTime, RecordDateUtil.YEAR_MONTH_DAY);
    String startT = RecordDateUtil.millisToString(startTime, RecordDateUtil.HOUR_MINUTE_SECOND);
    String endT = RecordDateUtil.millisToString(endTime, RecordDateUtil.HOUR_MINUTE_SECOND);
    mp4FileNameBuilder.append(Constants.BASE_PATH).append(Constants.SOUND_RECORDER).append(File.separator)
                    .append(audioNamePrefix).append("_").append(vin).append("_").append(day).append("_")
                    .append(startT).append("_").append(endT).append(".mp4");
    Logger.tag(TAG).i("create mp4 file name = " + mp4FileNameBuilder);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

RecordDateUtil时间工具类

public class RecordDateUtil {
    public static final String YEAR_MONTH_DAY = "yyyyMMdd";
    public static final String HOUR_MINUTE_SECOND = "HHmmss";

    public static long stringToMillis(String dateStr, String format) {
        return DateUtils.stringToMillis(dateStr, format);
    }

    public static String millisToString(Long timeMillis, String format) {
        return DateUtils.millisToString(timeMillis, format);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

创建转换的实例,新开一个线程执行对应的操作,录制的时间比较长,pcm文件录了八个通道的数据,文件特别大,需要对原始文件进行处理,再进行编码转换
在这里插入图片描述
16bit的,所以每个通道2个字节。每幀数据就是16个字节。按照上图通道顺序排列的。

	//创建转换的实例
	PcmToMp4Util pcmToMp4 = new PcmToMp4Util(currentPcmFile, mp4FileNameBuilder.toString());
	RxUtils.handleRecord(Completable.create(emitter -> {
                Logger.tag(TAG).i(" start original pcm data convert to byte array");
                //文件太大,直接将原始的pcm数据转换成字节数组,会导致内存溢出,所以需要将大文件先切割,再转换
                List<File> fileList = SoundRecordFileUtil.splitFile(currentPcmFile,
                        Constants.BASE_PATH, Constants.SMALL_FILE_SIZE);

                Logger.tag(TAG).i(" clear original data ");
                //将原始的pcm文件的数据清掉
                SoundRecordFileUtil.clearFileContent(currentPcmFile);

                Logger.tag(TAG).i(" add file list date to pcm file ");
                //遍历小文件列表,将数据加入到pcm文件中
                SoundRecordFileUtil.addDataToPcm(currentPcmFile, fileList);

                Logger.tag(TAG).i(" delete files ");
                //将原来的小文件们都删除
                SoundRecordFileUtil.deleteFiles(fileList);

                Logger.tag(TAG).i(" pcm conversion to mp4 ");
                //将pcm文件转换为mp4文件
                startPcmToMp4(pcmToMp4);

                Logger.tag(TAG).i(" delete original pcm file ");
                //最后将pcm文件删除
                SoundRecordFileUtil.deleteFile(currentPcmFile);
            }));
    private void startPcmToMp4(PcmToMp4Util pcmToMp4) {
            pcmToMp4.startPcmToMp4();
    }        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

处理pcm原始大文件的工具类

public class SoundRecordFileUtil {
    private static final String TAG = SoundRecordFileUtil.class.getSimpleName();

    public static void createNewDir(String dirPath) {
        File dirFile = new File(dirPath);
        if (!dirFile.exists()) {
            dirFile.mkdir();
        }
    }

    /**
     *
     * @param file : 原始的大文件
     * @param desPath : 生成的小文件的目录
     * @param byteNum : 定义的字节数组的大小
     * @return
     */
    public static List<File> splitFile(File file, String desPath, int byteNum) {
        List<File> fileList = new ArrayList<>();
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            //创建规定大小的byte数组
            byte[] b = new byte[byteNum];
            int len;
            //name为以后的小文件命名做准备
            //遍历将大文件读入byte数组中,当byte数组读满后写入对应的小文件中
            while ((len = fis.read(b)) != -1) {
                File newFile = new File(desPath + System.currentTimeMillis() + ".pcm");
                FileOutputStream fos = new FileOutputStream(newFile);
                //将byte数组写入对应的小文件中
                fos.write(b, 0, len);
                fos.flush();
                //结束资源
                fos.close();
                fileList.add(newFile);
            }
            return fileList;
        } catch (IOException e) {
            Logger.tag(TAG).i(" IOException " + e.getMessage());
            return null;
        } finally {
            try {
                if (fis != null) {
                    //结束资源
                    fis.close();
                }
            } catch (IOException e) {
                Logger.tag(TAG).i(" close IOException = " + e.getMessage());
            }
        }
    }

    public static void addDataToPcm(File file, List<File> fileList) {
        for (int i = 0; i < fileList.size(); i++) {
            byte[] bytes = fileConvertToByteArray(fileList.get(i));
            byte[] newByteArray = byteArrayConvertToNewByteArray(bytes,
                    Constants.PCM_SPLIT_STEP, Constants.INT_FOUR, Constants.INT_FIVE);
            writeNewDataToFile(file, newByteArray);
        }
    }

    public static byte[] fileConvertToByteArray(File file) {
        byte[] data = null;

        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            int len;
            byte[] buffer = new byte[Constants.BYTE_ARRAY_SIZE];
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            data = baos.toByteArray();
            fis.close();
            baos.close();
        } catch (Exception e) {
            Logger.tag(TAG).i(" fileConvertToByteArray exception " + e.getMessage());
        }

        return data;
    }

    /**
     * @param bytes         : 原来的字节数组
     * @param step          : 截取数据的步长
     * @param passageWayOne : 通道的索引1
     * @param passageWayTwo : 通道的索引2
     * @return
     */
    public static byte[] byteArrayConvertToNewByteArray(byte[] bytes, int step,
                                                        int passageWayOne, int passageWayTwo) {
        byte[] newBytes = new byte[bytes.length / step * Constants.INT_TWO];
        for (int i = 0, k = 0; i < bytes.length; i += step, k += Constants.INT_TWO) {
            newBytes[k] = bytes[i + passageWayOne];
            newBytes[k + 1] = bytes[i + passageWayTwo];
        }
        return newBytes;
    }

    public static void clearFileContent(File file) {
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write("");
            fileWriter.flush();
            fileWriter.close();
        } catch (IOException e) {
            Logger.tag(TAG).i(" clearFileContent exception " + e.getMessage());
        }
    }

    public static void writeNewDataToFile(File file, byte[] data) {
        OutputStream fos = null;
        try {
            fos = new FileOutputStream(file, true);
            fos.write(data);
            fos.flush();
        } catch (IOException e) {
            Logger.tag(TAG).i(" writeNewDataToFile IOException " + e.getMessage());
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    Log.i(TAG, " writeNewDataToFile close IOException " + e.getMessage());
                }
            }
        }
    }

    public static void deleteFiles(List<File> fileList) {
        for (int i = 0; i < fileList.size(); i++) {
            File file = fileList.get(i);
            if (file.exists() && file.isFile()) {
                file.delete();
            }
        }
    }

    public static boolean deleteFile(File file) {
        if (file.exists() && file.isFile()) {
            return file.delete();
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150

将pcm音频文件转换为mp4文件的工具类

public class PcmToMp4Util {
    private static final String TAG = PcmToMp4Util.class.getSimpleName();

    private File pcmFile, mp4File;
    private FileInputStream fis = null;
    private FileOutputStream fos = null;
    private MediaCodec encodeCodec;

    public PcmToMp4Util(File pcmFile, String mp4Path) {
        this.pcmFile = pcmFile;
        mp4File = new File(mp4Path);
        if (!mp4File.exists()) {
            try {
                mp4File.createNewFile();
                Logger.tag(TAG).i(" PCMToAAC acc file create new file");
            } catch (IOException e) {
                Logger.tag(TAG).i(" createNewFile e = " + e.getMessage());
            }
        }
    }


    public void startPcmToMp4() {
        if (pcmFile == null || !pcmFile.exists()) {
            Logger.tag(TAG).i(" startPcmToMp4 pcm file not exist ");
            return;
        }
        Logger.tag(TAG).i(" start pcm to mp4 ");
        try {
            //pcm文件获取
            fis = new FileInputStream(pcmFile);
            fos = new FileOutputStream(mp4File);

            /*
             *手动构建编码Format,参数含义:mine类型、采样率、通道数量
             *设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
             */
            MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,
                    Constants.CONTENT_SAMPLING_RATE, Constants.INT_ONE);
            encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            //比特率 声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标
            encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, Constants.CONTENT_BIT_RATE);
            //最大的缓冲区大小,如果inputBuffer大小小于我们定义的缓冲区大小,可能报出缓冲区溢出异常
            encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, Constants.BUFFER_SIZE);

            //构建编码器
            encodeCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
            //数据格式,surface用来渲染解析出来的数据;加密用的对象;标志 encode :1 decode:0
            encodeCodec.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            //用于描述解码得到的byte[]数据的相关信息
            MediaCodec.BufferInfo encodeBufferInfo = new MediaCodec.BufferInfo();
            //启动编码
            encodeCodec.start();

            /*
             * 同步方式,流程是在while中
             * dequeueInputBuffer -> queueInputBuffer填充数据 -> dequeueOutputBuffer -> releaseOutputBuffer
             */
            boolean hasAudio = true;
            byte[] pcmData = new byte[Constants.BUFFER_SIZE];
            while (true) {
                //所有的数据都运进数据工厂后不再添加
                if (hasAudio) {
                    //从pcm文件中,获取一组输入缓冲区
                    ByteBuffer[] inputBuffers = encodeCodec.getInputBuffers();
                    //返回当前可用的输入缓冲区的索引,参数0表示立即返回,小于0无限等待输入缓冲区的可用性,大于0表示等待的时间
                    int inputIndex = encodeCodec.dequeueInputBuffer(0);
                    //如果返回的是-1,表示当前没有可用的缓冲区
                    if (inputIndex != -1) {
                        Logger.tag(TAG).i("found the input cart index = " + inputIndex);
                        //将MediaCodec数据取出来放到这个缓冲区里
                        ByteBuffer inputBuffer = inputBuffers[inputIndex];
                        //清除里面旧的东西
                        inputBuffer.clear();
                        //将pcm数据读取到字节数组中,返回值为读取到的缓冲区的总字节数
                        int size = fis.read(pcmData);
                        //size小于0表示没有更多数据
                        if (size < 0) {
                            //当前pcm已经读取完了
                            Logger.tag(TAG).i("the current pcm has been read completely");
                            encodeCodec.queueInputBuffer(inputIndex, 0, 0, 0,
                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            hasAudio = false;
                        } else {
                            inputBuffer.limit(size);
                            inputBuffer.put(pcmData, 0, size);
                            Logger.tag(TAG).i("Audio data has been read, "
                                    + "and the current length of the audio data is:" + size);
                            //告诉工厂数据的序号、偏移量、大小、演示时间、标志等
                            encodeCodec.queueInputBuffer(inputIndex, 0, size, 0, 0);
                        }
                    } else {
                        Logger.tag(TAG).i("no available input carts");
                    }
                }

                //数据工厂已经把数据运进去了,但是是否加工转换成我们想要的数据(mp4)还是未知的,像输入缓冲流那样对输出缓冲流做类似的操作
                int outputIndex = encodeCodec.dequeueOutputBuffer(encodeBufferInfo, 0);
                switch (outputIndex) {
                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                        Logger.tag(TAG).i("the format of the output has been changed" + encodeCodec.getOutputFormat());
                        break;
                    case MediaCodec.INFO_TRY_AGAIN_LATER:
                        Logger.tag(TAG).i("timed out not obtained");
                        break;
                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                        Logger.tag(TAG).i("output buffer changed");
                        break;
                    default:
                        Logger.tag(TAG).i("The encoded data has been obtained, "
                                + "and the current parsed data length is:" + encodeBufferInfo.size);
                        //获取一组字节输出缓冲区数组
                        ByteBuffer[] outputBuffers = encodeCodec.getOutputBuffers();
                        //拿到当前装满数据的字节缓冲区
                        ByteBuffer outputBuffer;
                        if (Build.VERSION.SDK_INT >= Constants.MIN_SDK_INT) {
                            outputBuffer = encodeCodec.getOutputBuffer(outputIndex);
                        } else {
                            outputBuffer = outputBuffers[outputIndex];
                        }
                        //将数据放到新的容器里,便于后期传输,aac编码中需要ADTS头部,大小为7
                        int outPacketSize = encodeBufferInfo.size + Constants.INT_SEVEN;
                        byte[] newAACData = new byte[outPacketSize];
                        //添加ADTS
                        addADTStoPacket(newAACData, outPacketSize);
                        //从ADTS后面开始插入编码后的数据,写入到字节数组中
                        outputBuffer.get(newAACData, Constants.INT_SEVEN, encodeBufferInfo.size);
                        outputBuffer.position(encodeBufferInfo.offset);
                        //清空当前字节输出缓冲区
                        outputBuffer.clear();
                        //数据通过io流,写入mp4文件中
                        fos.write(newAACData);
                        fos.flush();
                        //把装载数据的装载物放回数据工厂里面
                        encodeCodec.releaseOutputBuffer(outputIndex, false);
                        break;
                }
                //当前的编码和解码已经完成
                if ((encodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    Logger.tag(TAG).i("Indicates that the current encoding and decoding have been completed");
                    break;
                }
            }
        } catch (IOException e) {
            Logger.tag(TAG).i(" error msg = " + e.getMessage());
        } finally {
            if (encodeCodec != null) {
                encodeCodec.stop();
                encodeCodec.release();
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 添加ADTS头
     */
    private void addADTStoPacket(byte[] packet, int packetLen) {
        // CHECKSTYLE.OFF: MagicNumber
        // AAC LC
        int profile = 2;
        // 44.1KHz
        int freqIdx = 4;
        // CPE
        int chanCfg = 2;
        // fill in ADTS data
        packet[0] = (byte) 0xFF;
        packet[1] = (byte) 0xF9;
        packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
        packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
        packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
        packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
        packet[6] = (byte) 0xFC;
        // CHECKSTYLE.ON: MagicNumber
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189

四、总结

如果你耐心看到了这里,那么从0到1编写一个录音软件,你差不多就已经掌握了。一千个人有一千个哈姆雷特,每个人对编码的理解应该都不大一样,总体代码还有一些地方可以优化,如果有好的意见或建议非常欢迎提出。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/684832
推荐阅读
相关标签
  

闽ICP备14008679号