AAC 音頻編碼保存和解碼播放

一. 編碼器 MediaCodec

MediaCodec 是 Android 提供的用于對音頻進行編解碼的類,屬于硬編解。MediaCodec 在編解碼的過程中使用了一組緩沖區(qū)來處理數(shù)據(jù)。如下圖所示:


image.png

基本使用流程如下:

// 1 創(chuàng)建編解碼器
MediaCodec.createByCodecName() // createEncoderByType , createDecoderByType

// 2 配置編解碼器
configure(@Nullable MediaFormat format, @Nullable Surface surface, @Nullable MediaCrypto crypto, int flags)

// 3 開始編解碼
start

// 4 循環(huán)處理數(shù)據(jù)
while(true) {
    dequeueInputBuffer// 獲取可用的輸入緩存區(qū) buffer 的下標 inputIndex
    getInputBuffers// 根據(jù) inputIndex 獲取可用的輸入緩沖區(qū) bytebuffer 
    bytebuffer.put // 放入數(shù)據(jù)
    queueInputBuffer // 將數(shù)據(jù)放入輸入緩沖區(qū)

    dequeueOutputBuffer // 獲取可用的輸出緩存區(qū) buffer 的下標 outputIndex
    getOutPutBuffers // 根據(jù) outputIndex 獲取可用的輸出緩沖區(qū) bytebuffer
    outputBuffer.get() // 獲取數(shù)據(jù)

    releaseOutputBuffer // 處理完成,釋放 buffer
}

// 5 終止
stop 

// 6 釋放編碼器使用的資源
release

二. MediaExtractor 媒體數(shù)據(jù)提取器

通過 MediaExtractor 可以將媒體文件的視頻和音頻數(shù)據(jù)分離,也可以獲取對應(yīng)的音頻格式或者視頻格式。主要 API 如下:

  • setDataSource : 設(shè)置數(shù)據(jù)源
  • getTrackCount:獲取文件的通道數(shù),音頻通道和視頻通道
  • getTrackFormat : 獲取指定通道的格式,比如音頻的格式或者是視頻的格式
  • getSampleTime: 獲取當(dāng)前幀的時間戳
  • readSampleData :將當(dāng)前幀數(shù)據(jù)寫入 byteBuffer
  • advance : 讀取下一幀
  • release : 釋放資源

基本使用流程如下:

// 1 設(shè)置數(shù)據(jù)源
setDataSource

// 2 獲取對應(yīng)的視頻或者音頻格式
getTrackFormat

// 3 定位到某條軌道
selectTrack 

// 4 讀取數(shù)據(jù)
while(true) {
    readSampleData 
    advance
}

// 5 釋放
release

三. 編碼保存

特別指出,由于 aac 的格式問題,如果保存到本地需要對每一幀添加 ADTS ,見 addADTStoPacket 方法

public class AacAudioRecord {
    private static final String TAG = "AacAudioRecord";
    private AudioRecord mAudioRecord;
    private MediaCodec mAudioEncoder;

    private volatile boolean mIsRecording = false;
    private ExecutorService mExecutorService;

    private int mAudioSource;
    private int mSampleRateInHz;
    private int mChannelConfig;
    private int mAudioFormat;
    private int mBufferSizeInBytes;
    private MediaFormat mMediaFormat;
    private String mFilePath;
    private File mFile;
    private FileOutputStream mFileOutputStream;
    private BufferedOutputStream mBufferedOutputStream;

    private BlockingQueue<byte[]> mDataQueue;


    public AacAudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) {
        mAudioSource = audioSource;
        mSampleRateInHz = sampleRateInHz;
        mChannelConfig = channelConfig;
        mAudioFormat = audioFormat;
        mBufferSizeInBytes = bufferSizeInBytes;
        mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
        try {
            mAudioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            mAudioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
            mMediaFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRateInHz, channelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
            //聲音中的比特率是指將模擬聲音信號轉(zhuǎn)換成數(shù)字聲音信號后,單位時間內(nèi)的二進制數(shù)據(jù)量,是間接衡量音頻質(zhì)量的一個指標
            mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//64000, 96000, 128000
            mMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSizeInBytes);
            mMediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
            mAudioEncoder.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        } catch (IOException e) {
            Log.e(TAG, "" + e.getMessage());
        }

        mDataQueue = new ArrayBlockingQueue<>(10);
        mExecutorService = Executors.newFixedThreadPool(2);
    }

    public void start(String filePath) {
        mFilePath = filePath;
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                startRecord();
            }
        });
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                startEncode();
            }
        });
    }

    public void stop() {
        mIsRecording = false;
    }

    private void startRecord() {
        mAudioRecord.startRecording();
        mIsRecording = true;
        byte[] buffer = new byte[2048];
        while (mIsRecording) {
            int len = mAudioRecord.read(buffer, 0, 2048);
            if (len > 0) {
                byte[] data = new byte[len];
                System.arraycopy(buffer, 0, data, 0, len);
                queueData(data);
            }
        }
        mAudioRecord.stop();
        mAudioRecord.release();
        mAudioRecord = null;
    }

    private void startEncode() {
        if (TextUtils.isEmpty(mFilePath)) {
            return;
        }
        mAudioEncoder.start();
        mFile = new File(mFilePath);
        if (mFile.exists()) {
            mFile.delete();
        }
        try {
            mFile.createNewFile();
            mFileOutputStream = new FileOutputStream(mFile);
            mBufferedOutputStream = new BufferedOutputStream(mFileOutputStream, 2048);
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] pcmData;
        int inputIndex;
        ByteBuffer inputBuffer;
        ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();

        int outputIndex;
        ByteBuffer outputBuffer;
        ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers();

        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        byte[] aacChunk;
        while (mIsRecording || !mDataQueue.isEmpty()) {
            pcmData = dequeueData();
            if (pcmData == null) {
                continue;
            }
            inputIndex = mAudioEncoder.dequeueInputBuffer(10_000);
            if (inputIndex >= 0) {
                inputBuffer = inputBuffers[inputIndex];
                inputBuffer.clear();
                inputBuffer.limit(pcmData.length);
                inputBuffer.put(pcmData);
                mAudioEncoder.queueInputBuffer(inputIndex, 0, pcmData.length, 0, 0);
            }

            outputIndex = mAudioEncoder.dequeueOutputBuffer(bufferInfo, 10_000);
            while (outputIndex >= 0) {
                outputBuffer = outputBuffers[outputIndex];
                outputBuffer.position(bufferInfo.offset);
                outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                aacChunk = new byte[bufferInfo.size + 7];
                addADTStoPacket(mSampleRateInHz, aacChunk, aacChunk.length);
                outputBuffer.get(aacChunk, 7, bufferInfo.size);
                try {
                    mBufferedOutputStream.write(aacChunk);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mAudioEncoder.releaseOutputBuffer(outputIndex, false);
                outputIndex = mAudioEncoder.dequeueOutputBuffer(bufferInfo, 10_000);
            }
        }
        try {
            mBufferedOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        IOUtil.close(mBufferedOutputStream);
        IOUtil.close(mFileOutputStream);
        mAudioEncoder.stop();
        mAudioEncoder.release();
        mAudioEncoder = null;
    }

    private byte[] dequeueData() {
        if (mDataQueue.isEmpty()) {
            return null;
        }
        try {
            return mDataQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void queueData(byte[] data) {
        try {
            mDataQueue.put(data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void addADTStoPacket(int sampleRateType, byte[] packet, int packetLen) {
        int profile = 2;
        int chanCfg = 2;
        packet[0] = (byte) 0xFF;
        packet[1] = (byte) 0xF9;
        packet[2] = (byte) (((profile - 1) << 6) + (sampleRateType << 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;
    }

四. 解碼播放


public class AacAudioPlayer {

    private AudioTrack mAudioTrack;
    private MediaCodec mAudioDecoder;

    private MediaExtractor mMediaExtractor;
    private int mStreamType = AudioManager.STREAM_MUSIC;
    private int mSampleRate = 44100;
    private int mChannelConfig = AudioFormat.CHANNEL_OUT_MONO;
    private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
    private int mMode = AudioTrack.MODE_STREAM;
    private int mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRate, mChannelConfig, mAudioFormat);
    private volatile boolean mIsPlaying = false;
    private ExecutorService mExecutorService;

    public AacAudioPlayer() {
        mAudioTrack = new AudioTrack(mStreamType, mSampleRate, mChannelConfig, mAudioFormat, mMinBufferSize, mMode);
        mMediaExtractor = new MediaExtractor();
        mExecutorService = Executors.newFixedThreadPool(1);
    }


    public void play(String filePath) {
        try {
            mMediaExtractor.setDataSource(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(0);
        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
        if (TextUtils.isEmpty(mimeType)) {
            return;
        }
        assert mimeType != null;
        if (mimeType.startsWith("audio")) {
            mMediaExtractor.selectTrack(0);
            mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
            mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, mChannelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
            mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 0);
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
            mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
            mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, 0);
            try {
                mAudioDecoder = MediaCodec.createDecoderByType(mimeType);
            } catch (IOException e) {
                e.printStackTrace();
            }
            mAudioDecoder.configure(mediaFormat, null, null, 0);
        }
        mAudioDecoder.start();
        mAudioTrack.play();
        mExecutorService.execute(new Runnable() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void run() {
                decode();
            }
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void decode() {
        MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();
        ByteBuffer inputBuffer;
        mIsPlaying = true;
        while (mIsPlaying) {
            int inputIndex = mAudioDecoder.dequeueInputBuffer(10_000);
            if (inputIndex < 0) {
                mIsPlaying = false;
                continue;
            }
            inputBuffer = mAudioDecoder.getInputBuffer(inputIndex);
            inputBuffer.clear();
            int sampleSize = mMediaExtractor.readSampleData(inputBuffer, 0);
            if (sampleSize > 0) {
                mAudioDecoder.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);
                mMediaExtractor.advance();
            } else {
                mIsPlaying = false;
            }
            int outputIndex = mAudioDecoder.dequeueOutputBuffer(decodeBufferInfo, 0);
            ByteBuffer outputBuffer;
            byte[] buffer;
            while (outputIndex >= 0) {
                outputBuffer = mAudioDecoder.getOutputBuffer(outputIndex);
                if (outputBuffer == null) {
                    break;
                }
                buffer = new byte[decodeBufferInfo.size];
                outputBuffer.get(buffer);
                outputBuffer.clear();
                mAudioTrack.write(buffer, 0, decodeBufferInfo.size);
                mAudioDecoder.releaseOutputBuffer(outputIndex, false);
                outputIndex = mAudioDecoder.dequeueOutputBuffer(decodeBufferInfo, 0);
            }
        }
        mIsPlaying = false;
        mAudioDecoder.stop();
        mAudioDecoder.release();
        mAudioDecoder = null;
    }

}

github demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容