AAC 音频编码保存和解码播放

2023-05-16

一. 编码器 MediaCodec

MediaCodec 是 Android 提供的用于对音频进行编解码的类,属于硬编解。MediaCodec 在编解码的过程中使用了一组缓冲区来处理数据。如下图所示:
image.png
基本使用流程如下:

// 1 创建编解码器
MediaCodec.createByCodecName() // createEncoderByType , createDecoderByType

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

// 3 开始编解码
start

// 4 循环处理数据
while(true) {
    dequeueInputBuffer// 获取可用的输入缓存区 buffer 的下标 inputIndex
    getInputBuffers// 根据 inputIndex 获取可用的输入缓冲区 bytebuffer 
    bytebuffer.put // 放入数据
    queueInputBuffer // 将数据放入输入缓冲区

    dequeueOutputBuffer // 获取可用的输出缓存区 buffer 的下标 outputIndex
    getOutPutBuffers // 根据 outputIndex 获取可用的输出缓冲区 bytebuffer
    outputBuffer.get() // 获取数据

    releaseOutputBuffer // 处理完成,释放 buffer
}

// 5 终止
stop 

// 6 释放编码器使用的资源
release

二. MediaExtractor 媒体数据提取器

通过 MediaExtractor 可以将媒体文件的视频和音频数据分离,也可以获取对应的音频格式或者视频格式。主要 API 如下:

  • setDataSource : 设置数据源
  • getTrackCount:获取文件的通道数,音频通道和视频通道
  • getTrackFormat : 获取指定通道的格式,比如音频的格式或者是视频的格式
  • getSampleTime: 获取当前帧的时间戳
  • readSampleData :将当前帧数据写入 byteBuffer
  • advance : 读取下一帧
  • release : 释放资源

基本使用流程如下:

// 1 设置数据源
setDataSource

// 2 获取对应的视频或者音频格式
getTrackFormat

// 3 定位到某条轨道
selectTrack 

// 4 读取数据
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);
            //声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标
            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

欢迎关注我的微信公众号【海盗的指针】

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AAC 音频编码保存和解码播放 的相关文章

随机推荐

  • Cartographer详解

    Cartographer 论文解读 一 Introduction 在建图上应用SLAM并不是一个新的概念 xff0c 这里不再作为本文的重点 本文的贡献在于 xff1a 提出了一种新的基于激光数据的回环检测方法 xff0c 这种方法可以减少
  • C语言实现TCP通信

    C语言通过socket编程实现TCP通信 服务端客户端通信例子 xff1a socket tcp 通信1 xff0c socket tcp通信2 xff0c udp使用讲解 xff0c socket udp通信例子 TCP IP协议 叫做传
  • KF与无迹卡尔曼滤波详解

    这是另一片写卡尔曼滤波的文章 xff0c 亮点在与总结的卡尔曼滤波的五个公式 xff0c 可通过上一篇理解卡尔曼滤波的推导原理 xff0c 本篇用来理解卡尔曼滤波的计算实现 1 xff0e 简介 Brief Introduction 在学习
  • 保存并查看Lego-Loam的三维点云地图

    Loam的安装及运行方法可以参考 https blog csdn net qq 36396941 article details 82973772 本文提供ROS wiki http wiki ros org loam velodyne上无
  • Could not find a package configuration file provided by “OpenCV“ with any of the following names

    CMake Error at opt ros kinetic share catkin cmake catkinConfig cmake 83 find package Could not find a package configurat
  • EKF的通俗理解

    导 xff1a ekf xff0c 扩展卡尔曼滤波简称 xff0c 应用非常广泛 xff1b 1 五个黄金公式 2 应用场合 1 xff09 找清楚模型 2 xff09 对准五个公式的公式 3 xff09 实现 xff1a 求革新值 xff
  • error: Could NOT find pugixml (missing: PUGIXML_LIBRARIES PUGIXML_INCLUDE_DIRS)

    解决办法 xff1a sudo apt get install libpugixml dev sudo apt get install libpugixml1v5
  • STM32小四轴超低成本方案开源项目

    先分享几个小四轴无人机项目 新唐M452飞控开源项目 xff0c 虽然完全开源但是还不够成熟 xff0c PID调节感觉还有些问题 助你轻松DIY四轴飞行器 新唐M452飞控套件评测 电路城 MWC 飞控 xff0c 采用arduino编程
  • Autoware 主要模块

    引言 本文参考Autoware wiki overview xff0c 主要描述了Autoware的整体框架和模块描述 xff0c 主要包括感知和规划两大部分 感知包括定位模块 xff0c 检测模块 xff0c 预测模块 定位模块使用3D
  • DWA算法原理

    DWA算法 局部路径规划简介 机器人在获得目的地信息后 xff0c 首先经过全局路径规划规划出一条大致可行的路线 xff0c 然后调用局部路径规划器根据这条路线及costmap的信息规划出机器人在局部时做出具体行动策略 xff0c ROS中
  • ROS与深度相机入门教程:(1)Ubuntu16.04 在ROS中驱动Intel D435i深度相机

    Intel在Github上开源了支持所有RealSense系列相机的SDK 包括了D400 SR300系列深度相机和T265双目跟踪相机 支持Linux Windows Mac OS以及Android 链接 https github com
  • CMakeLists生成和载入动态链接库

    CMakeLists生成和载入动态链接库 生成动态链接库 新建一个文件夹 xff0c 暂且命名为 makeDllLib 文件夹中放入三个文件 c和 h和 def 其中 def文件是非必须的 xff0c 但它有利于生成 lib文件和导出函数
  • 串口传输 波特率 延时时间的设置

    在进行串口传输的时候 xff0c 波特率太低接收不到 xff0c 波特率太高又丢包 首先 xff0c 传输的报文需要多少时间 起始位1 xff0c 停止位1 xff0c 数据位8 xff0c 则传输时间为 xff1a 1000 xff08
  • 阶段进度条

    public class PhaseProgressView extends View 节点连线宽度 private int mLineWidth 节点个数 private int mNodeNum 选中节点位置 private int m
  • 计算机网络实验——基于TCP协议的socket编程

    一 实验目的 1 实现一个能够在局域网中进行点对点聊天的实用程序 2 熟悉c 43 43 Java等高级编程语言网络编程的基本操作 3 基本了解对话框应用程序的编写过程 4 实现TCP套接字编程 二 实验内容 xff08 一 xff09 实
  • GPRMC 格式

    数据示例 GPRMC 081057 000 A 3117 2144 N 12133 2691 E 0 02 0 00 140521 D 68 在数据 GPRMC lt 1 gt lt 2 gt lt 3 gt lt 4 gt lt 5 gt
  • 音视频开发基础概念

    对一个初学者来说 xff0c 刚刚接触音视频的学习难免会遇到各种个样的术语 xff0c 一开始我也是云里雾里的 xff0c 到现在一点一点接触积累 xff0c 形成一个基本的认识 本文并没有什么高深和详细的知识点 xff0c 旨在记录一些音
  • 音频数据采集-AudioRecord

    一 AudioRecord 和 MediaRecorder Android 提供了两个 API 用于录音 xff0c AudioRecord 和 MediaRecorder AudioRecord 能够获取原始的 PCM 数据 xff0c
  • vector用法

    介绍 1 vector是表示可变大小数组的序列容器 2 就像数组一样 xff0c vector也采用的连续存储空间来存储元素 也就是意味着可以采用下标对vector的元素进行访问 xff0c 和数组一样高效 但是又不像数组 xff0c 它的
  • AAC 音频编码保存和解码播放

    一 编码器 MediaCodec MediaCodec 是 Android 提供的用于对音频进行编解码的类 xff0c 属于硬编解 MediaCodec 在编解码的过程中使用了一组缓冲区来处理数据 如下图所示 基本使用流程如下 xff1a