Android语音聊天室源码开发,如何实现音频解码?

在语音聊天室源码中,完整的流媒体传输涉及到的环节还是非常多的,不仅于此,每个环节都至关重要,今天我们主要来讲解一下在语音聊天室源码开发中,是如何实现音频解码的,主要用到的音频解码标准是AAC。

一、MediaCodec

MediaCodec不光能编码,同样也能解码。
在这里插入图片描述

之前语音聊天室源码编码,我们是将AudioRecord中读取数据送入编码器,接着再从编码器中获取输出的数据,这样循环,最终得到编码后的aac文件。

那么对于语音聊天室源码解码,我们就可以将编码后的aac文件传入解码器,接着再从解码器中获取输出的数据传入AudioTrack,这样循环,就可以完整的播放该aac文件。

此时产生了一个问题,我们如何将aac文件送入解码器呢,直接读文件是否可以,就像AudioTrack播放Pcm文件时,直接读文件数据,传入AudioTrack中播放。

当然,直接读文件是可以的,我们只需要在语音聊天室源码开发时设置好音频解码的相关参数,比如采样率、声道设置、编码格式等。
不过,Android提供了一种更为方便的组件,MediaExtractor,它可以从文件中获取编码的音频或视频,并一帧一帧解析出来

那么,下面我们将使用MediaExtractor来进行语音聊天室源码中aac音频文件的读取。

首先,介绍下MediaExtractor的使用步骤:

  • 初始化实例
  • 设置文件路径
  • 遍历所有的track,根据需要,找到指定的track,并调用selectTrack()方法
  • 调用readSampleData()方法读取当前帧数据
  • 读取下一帧,调用advance()
  • 读取结束,则释放资源

二、音频解码

通过上面的描述,我们对语音聊天室源码音频解码的步骤有了一定了解,我们先来回顾下AudioTrack的使用步骤:

  1. 开启子线程
  2. 构建实例
  3. 开始播放
  4. 循环读取文件数据,并写入AudioTrack
  5. 停止播放,释放资源

那么,对于语音聊天室源码音频解码播放,步骤如下:

  1. 开启子线程
  2. 构建实例
  3. 从MediaExtractor中找到指定的track
  4. 开始播放、解码
  5. 循环从MediaExtractor中读取数据,并送入解码器
  6. 解码完成后,数据写入AudioTrack中进行播放
  7. 停止播放、解码,释放资源

下面,就对上述步骤,一一讲解

2.1 开启子线程

语音聊天室源码解码的整个流程也是要在子线程中执行的

private static class DecodeThread extends Thread {
    public DecodeThread(){}
}

2.2 配置必要参数

private static class DecodeThread extends Thread {
    private static final long TIMEOUT_MS = 2000L;
    private MediaExtractor mediaExtractor;
    private MediaCodec mediaCodec;
    private AudioTrack audioTrack;
    private final String path;
    /**
     * 音频流格式(一般使用music)
     */
    private final int streamType;
    /**
     * 采样率
     */
    private final int sampleRateInHz;
    /**
     * 声道设置
     */
    private final int channelConfig;
    /**
     * 编码格式
     */
    private final int audioFormat;
    /**
     * 播放模式(一般使用流模式)
     */
    private final int mod;
    /**
     * 音频缓存大小
     */
    private int bufferSizeInBytes;
    /**
     * 音频格式
     */
    private MediaFormat format;
    private String mime;
    /**
     * 是否停止解码
     */
    private boolean isStopDecode = false;
    /**
     * 构造方法(传入必要的参数)
     */
    public DecodeThread(String path,
                        int streamType,
                        int sampleRateInHz,
                        int channelConfig,
                        int audioFormat,
                        int mod
    ) {
        this.path = path;
        this.streamType = streamType;
        this.sampleRateInHz = sampleRateInHz;
        this.channelConfig = channelConfig;
        this.audioFormat = audioFormat;
        this.mod = mod;
    }
}

参数和AudioTrack播放Pcm时基本一致,只是多了两个组件MediaExtractor和MediaCodec

2.3 初始化

重写Thread的run方法

@Override
public void run() {
    super.run();
    initMediaExtractor();
    initMediaCodec();
    initAudioTrack();
    decode();
}

run方法中,进行了一系列的初始化操作,最后一个方法decode()是真正开始解码
# initMediaExtractor()

private void initMediaExtractor() {
    if (TextUtils.isEmpty(path)) {
        return;
    }
    try {
        mediaExtractor = new MediaExtractor();
        mediaExtractor.setDataSource(path);
        int trackCount = mediaExtractor.getTrackCount();
        for (int i = 0; i < trackCount; i++) {
            MediaFormat format = mediaExtractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (!TextUtils.isEmpty(mime) && mime.startsWith("audio/")) {
                mediaExtractor.selectTrack(i);
                this.format = format;
                this.mime = mime;
                break;
            }
        }
    } catch (IOException e) {
        Log.e(TAG, "initMediaExtractor: ", e);
        mediaExtractor = null;
        format = null;
        mime = null;
    }
}

在该方法中,我们对MediaExtractor做了初始化操作,并找到了对应audio的track
# initMediaCodec

private void initMediaCodec() {
    if (TextUtils.isEmpty(mime) || format == null) {
        return;
    }
    try {
        mediaCodec = MediaCodec.createDecoderByType(mime);
        mediaCodec.configure(format, null, null, 0);
    } catch (IOException e) {
        e.printStackTrace();
        mediaCodec = null;
    }
}

可以看到,初始化MediaCodec的逻辑比较简单,这是因为我们利用了从MediaExtractor中获取的MediaFormat去配置MediaCodec,如果是从语音聊天室源码文件读取的话,MediaFormat还需要我们自己配置

# initAudioTrack()

private void initAudioTrack() {
    bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
    audioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mod);
}

2.4 播放

进入到decode()方法后,就正式开始播放

# decode()

private void decode() {
    if (mediaExtractor == null || mediaCodec == null || audioTrack == null) {
        return;
    }
    long startMs = System.currentTimeMillis();
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    mediaCodec.start();
    audioTrack.play();
    for (; ; ) {
        if (isStopDecode) {
            release();
            break;
        }
        int inputBufferId = mediaCodec.dequeueInputBuffer(TIMEOUT_MS);
        if (inputBufferId >= 0) {
            ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferId);
            int readSize = -1;
            if (inputBuffer != null) {
                readSize = mediaExtractor.readSampleData(inputBuffer, 0);
            }
            if (readSize <= 0) {
                mediaCodec.queueInputBuffer(
                        inputBufferId,
                        0,
                        0,
                        0,
                        MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                isStopDecode = true;
            } else {
                mediaCodec.queueInputBuffer(inputBufferId, 0, readSize, mediaExtractor.getSampleTime(), 0);
                mediaExtractor.advance();
            }
        }
        int outputBufferId = mediaCodec.dequeueOutputBuffer(info, TIMEOUT_MS);
        if (outputBufferId >= 0) {
            ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId);
            if (outputBuffer != null && info.size > 0) {
                while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                    try {
                        sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }
                byte[] data = new byte[info.size];
                outputBuffer.get(data);
                outputBuffer.clear();
                audioTrack.write(data, 0, info.size);
            }
            mediaCodec.releaseOutputBuffer(outputBufferId, false);
        }
    }
}

语音聊天室源码解码过程中,也是通过死循环不断的读取数据,并将解码后的数据传入AudioTrack播放的过程,不过,需要注意的一点是,在语音聊天室源码解码完成后,数据不能马上传入AudioTrack,而是要通过解码时间与当前时间做对比,进行延时解码,如果不进行这一操作,语音聊天室源码播放出的音频会很快。

以上就是“Android语音聊天室源码开发,如何实现音频解码?”的全部内容,希望对大家有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值