MediaCodec AudioRecord 硬编 AAC 格式音频(一)详解手机开发

网上有很多关于 MediaCodec 硬编解 AAC 格式音频的文章,但良莠不齐,以下时自己学习 MediaCodec 硬编 AAC 格式音频过程的记录和一些参考的文章。

介绍

AAC
一种音频压缩格式,区别于无损的 PCM 数据格式,其中分成了两种:ADTS 和 ADIF 格式

  • ADTS:Audio Data Interchange Format 音频数据交换格式,有一个统一的头部再带上数据流,如果想解析,必须得到所有的数据后,先寻找同步头,再解码。
  • ADIF:Audio Data Transport Stream 音频数据传输流,每一个帧都有一个头部,所以可以在任意帧上进行解码。

AudioRecord
Android 中录制无损音频 PCM 格式的工具
关于 AudioRecord 录音的流程和参数可以参考之前的文章 Android AudioRecord、AudioTrack 录制播放音频

MediaCodec
MediaCodec 是 Android 用于音视频编解码的一套偏底层的 API,直接利用硬件加速进行编解码。

我们所需要做的处理流程:

  1. dequeueInputBuffer:从 input 缓存区申请 buffer 编号 Index
  2. getInputBuffer:用编号 Index 取得输入的缓冲区,将需要编码的数据写入 buffer
  3. queueInputBuffer:将 buffer 入 MediaCodec 的队列,MediaCodec 会从 buffer 中取数据处理
  4. dequeueOutputBuffer:从 output 缓冲区申请 buffer 编号
  5. getOutputBuffer:用编号 Index 取得输出的缓冲区,buffer 中就是处理后的数据
  6. releaseOutputBuffer:将该 buffer 放回 output 缓冲区队列

示意图

流程

1. AudioRecord 录制 PCM 格式的音频

(1)创建 AudioRecord:

// 算出所需的最小的缓冲区大小 
int minBufferSize = AudioRecord.getMinBufferSize( 
    44100, // 采样率 
    AudioFormat.CHANNEL_IN_MONO, // 通道数 
    AudioFormat.ENCODING_PCM_16BIT 
    ); 
 
mRecorder = new AudioRecord( 
    MediaRecorder.AudioSource.DEFAULT, // 音频源 
    44100, // 采样率 
    AudioFormat.CHANNEL_IN_MONO, // 声道 
    AudioFormat.ENCODING_PCM_16BIT, // 采样大小 
    minBufferSize  
    );

(2)开始录音

mRecorder.startRecording();

(3)读取 mRecorder 的一帧帧数据,实时传送给 MediaCodec 后续处理

while (isRecording && (len = mRecorder.read(bytesBuffer, 0, minBufferSize)) > 0) { 
    // 后续转码处理 
}
2. 通过 MediaCodec 硬编成 AAC 格式,并保存为外部文件

(1)MediaCodec 创建流程

mEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);  
//指定创建的MediaCodec类型 
 
MediaFormat format = MediaFormat.createAudioFormat( 
    MediaFormat.MIMETYPE_AUDIO_AAC, 
    44100, //采样率 
    1 // 声道数量,一开始写成了AudioFormat.CHANNEL_IN_MONO 
); 
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC); 
// 封装可用于编解码器组件的配置文件 
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);  
//码率:声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标 
format.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//传入的数据最大值,可以修改 
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, minBufferSize * 2); 
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
mEncoder.start();

(2)MediaCodec 具体转码流程

while (isRecording && (len = mRecorder.read(bytesBuffer, 0, minBufferSize)) > 0) {  
// 不断从Recorder读取数据 
    int inputBufferIndex = mEncoder.dequeueInputBuffer(100000); 
    // 申请输入的buffer, 等待时间,0:不等待,-1:一直而等待 
    if (inputBufferIndex >=0 ) { 
        ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex); 
        inputBuffer.clear(); // 清除数据,否则可能是上次写入的数据 
        inputBuffer.put(bytesBuffer); 
        inputBuffer.limit(len);// 设定上限值 
        mEncoder.queueInputBuffer(inputBufferIndex, 0, len, System.nanoTime(), 0); // 第三个参数为时间戳,这里是使用当前使劲按 
    } else { 
        isRecording = false; 
    } 
 
    // 申请处理完成后输出的buffer 
    int outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0); 
    // 可能不能一次性处理完,所以循环读取 
    while (outputBufferIndex >= 0) { 
        int outBitsSize = mBufferInfo.size; 
        int outPacketSize = outBitsSize + 7; // ADTS头部是7个字节 
        ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex); 
        outputBuffer.position(mBufferInfo.offset); 
        outputBuffer.limit(mBufferInfo.offset + outBitsSize); 
 
        byte[] outData = new byte[outPacketSize]; 
        // 需要为adts帧加上头部 
        addADTStoPacket(outData, outPacketSize); 
 
        outputBuffer.get(outData, 7, outBitsSize); 
        outputBuffer.position(mBufferInfo.offset); 
        // 写入文件 
        mFileStream.write(outData); 
        // 释放输出的buffer 
        mEncoder.releaseOutputBuffer(outputBufferIndex, false); 
        outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0); 
    } 
} 

源码:

public class AacRecordActivity extends AppCompatActivity{
    
    private final static String TAG = AacRecordActivity.class.getSimpleName(); 
    public static String[] MICROPHONE = {Manifest.permission.RECORD_AUDIO}; 
    public static String[] STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; 
 
    private Button mRecordBtn; 
    private MyAudioRecorder mAudioRecorder; 
 
    @Override 
    protected void onCreate(@Nullable Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_aac); 
        mRecordBtn = findViewById(R.id.aac_recording_btn); 
        mAudioRecorder = new MyAudioRecorder(); 
        mRecordBtn.setOnTouchListener(new View.OnTouchListener() { 
            @Override 
            public boolean onTouch(View view, MotionEvent motionEvent) { 
                int action = motionEvent.getAction(); 
                if (action == MotionEvent.ACTION_DOWN) { 
                    checkRecordPermission(); 
                    mRecordBtn.setText("正在录制"); 
                    startRecord(); 
                } else if (action == MotionEvent.ACTION_UP) { 
                    mRecordBtn.setText("录音"); 
                    stopRecord(); 
                } 
                return false; 
            } 
        }); 
    } 
 
    private void startRecord() { 
        new Thread(new Runnable() { 
            @Override 
            public void run() { 
                mAudioRecorder.start(); 
            } 
        }).start(); 
    } 
 
    private void stopRecord() { 
        mAudioRecorder.stop(); 
    } 
 
    private void checkRecordPermission() { 
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { 
            ActivityCompat.requestPermissions(this, MICROPHONE, 1); 
            return; 
        } 
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 
            ActivityCompat.requestPermissions(this, STORAGE, 1); 
            return; 
        } 
    } 
 
    private class MyAudioRecorder {
    
        private AudioRecord mRecorder; 
        private MediaCodec mEncoder; 
        private OutputStream mFileStream; 
        private boolean isRecording; 
 
        public void start() { 
            try { 
                isRecording = true; 
                int minBufferSize = AudioRecord.getMinBufferSize(44100, 1, AudioFormat.ENCODING_PCM_16BIT); 
                mRecorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 44100,  AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize); 
                if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) { 
                    Log.e(TAG, "initRecord: mAudioRecord init failed"); 
                    isRecording = false; 
                    return; 
                } 
                mRecorder.startRecording(); 
 
                mEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC); 
                MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100, 1); 
                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC); 
                format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 
                format.setInteger(MediaFormat.KEY_BIT_RATE, 96000); 
                format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, minBufferSize * 2); 
                mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
                mEncoder.start(); 
 
                mFileStream = new FileOutputStream(getSDPath() + "/acc_encode.mp4"); 
 
                MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); 
                byte[] bytesBuffer = new byte[minBufferSize]; 
                int len = 0; 
                while (isRecording && (len = mRecorder.read(bytesBuffer, 0, minBufferSize)) > 0) { 
                    int inputBufferIndex = mEncoder.dequeueInputBuffer(100000); 
                    if (inputBufferIndex >=0 ) { 
                        ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex); 
                        inputBuffer.clear(); 
                        inputBuffer.put(bytesBuffer); 
                        inputBuffer.limit(len); 
                        mEncoder.queueInputBuffer(inputBufferIndex, 0, len, System.nanoTime(), 0); 
                    } else { 
                        isRecording = false; 
                    } 
 
                    int outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0); 
                    while (outputBufferIndex >= 0) { 
                        int outBitsSize = mBufferInfo.size; 
                        int outPacketSize = outBitsSize + 7; // ADTS头部是7个字节 
                        ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex); 
                        outputBuffer.position(mBufferInfo.offset); 
                        outputBuffer.limit(mBufferInfo.offset + outBitsSize); 
 
                        byte[] outData = new byte[outPacketSize]; 
                        addADTStoPacket(outData, outPacketSize); 
 
                        outputBuffer.get(outData, 7, outBitsSize); 
                        outputBuffer.position(mBufferInfo.offset); 
                        mFileStream.write(outData); 
                        mEncoder.releaseOutputBuffer(outputBufferIndex, false); 
                        outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0); 
                    } 
                } 
                release(); 
            } catch (FileNotFoundException e) { 
                e.printStackTrace(); 
            } catch (IOException e) { 
                e.printStackTrace(); 
            } 
        } 
 
        private void stop() { 
            isRecording = false; 
        } 
 
        private void release() throws IOException { 
            if (mFileStream != null) { 
                mFileStream.flush(); 
                mFileStream.close(); 
            } 
            if (mRecorder != null && mRecorder.getState() == AudioRecord.STATE_INITIALIZED) { 
                mRecorder.stop(); 
                mRecorder.release(); 
                mRecorder = null; 
            } 
            if (mEncoder != null) { 
                mEncoder.stop(); 
            } 
            runOnUiThread(new Runnable() { 
                @Override 
                public void run() { 
                    Toast.makeText(AacRecordActivity.this, "录制完毕", Toast.LENGTH_SHORT).show(); 
                } 
            }); 
        } 
    } 
 
    public String getSDPath() { 
        // 判断是否挂载 
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 
            return Environment.getExternalStorageDirectory().getAbsolutePath(); 
        } 
        return Environment.getRootDirectory().getAbsolutePath(); 
    } 
 
    private void addADTStoPacket(byte[] packet, int packetLen) { 
        int profile = 2;  //AAC LC 
        int freqIdx = 4;  //44100 
        int chanCfg = 2;  //CPE 
        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; 
    }

参考文章:

《音频数据的编解码》 云图网
《MediaCodec 解码 aac》 云图网

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6281.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论