音频编码

音频的基础知识

我们都知道音频是指通过声波传播的声音信号,我们要把声音的数据采集下来,进行传输或保存起来,就需要对声音进行编码。

在编码之前,先要对声音进行采集,采集就涉及到以下一些要素:

  1. 采样率(Sample Rate):采样率表示每秒钟对声音信号进行采样的次数,单位为赫兹(Hz)。常见的采样率有 44.1 kHz(CD质量)、48 kHz(视频、音频制作)、96 kHz(高保真音频)等。
  2. 位深度(Bit Depth):位深度表示每个采样点的样本精度,即用多少位来表示一个采样点的数值。常见的位深度有 16 位、24 位、32 位等。
  3. 声道:
    • 单声道(Mono):只包含一个声道的音频信号。
    • 立体声(Stereo):包含两个声道,通过左右声道的不同分配实现立体感。
    • 多声道(Multi-channel):包含多个声道,例如 5.1 声道、7.1 声道等,常用于影院和环绕音效。

采集完后,得到的音频数据是PCM数据,可以通过MediaCodec 进行编码得到AAC数据,这样编码就完成了,我们可以把编码后的音频数据通过MediaMuxer 写入MP4 文件中,这样就可以播放音频了。

Android 中如何实现音频编码

  1. 使用 Android 给我们提供的 AudioRecord来完成音频的录制;
kotlin 复制代码
private val audioRecord:AudioRecord by lazy {
    AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes)
}

这样就创建了AudioRecord对象,里面的具体参数在下面的API有详细说明。 2. 创建编码器

kotlin 复制代码
private val mediaCodec: MediaCodec by lazy {
    val mimeType = MediaFormat.MIMETYPE_AUDIO_AAC // 音频编码格式
    val mediaCodec = MediaCodec.createEncoderByType(mimeType)
    val format = MediaFormat.createAudioFormat(mimeType, sampleRateInHz, 2)// 是双声道的声道数是2
    format.setInteger(MediaFormat.KEY_BIT_RATE, audioBitRate)// 设置码率
    format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)//  AAC 编码的配置文件
    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSizeInBytes)
    mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    mediaCodec
}
  1. 创建MediaMuxer对象,用于MP4文件的写入
kotlin 复制代码
private val mediaMuxer: MediaMuxer by lazy {
    try {
        val mediaMuxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
        mediaMuxer.setOrientationHint(90)
        mediaMuxer
    } catch (e: IOException) {
        throw java.lang.RuntimeException(e)
    }
}
  1. 开启AudioRecord、MediaCodec、MediaMuxer 在线程中读书数据进行编码,然后写入到MP4文件中
scss 复制代码
fun start(){
    nanoTime = System.nanoTime()
    // 开启编码
    mediaCodec.start()
    // 开始录音
    audioRecord.startRecording()
    startEncodeThread()
}
  1. 开启一个线程读取数据
scss 复制代码
private fun startEncodeThread() {
    isRecord = true
    Thread {
        while (isRecord){
            encodeAudio()
        }
        audioRecord.stop()
        audioRecord.release()
        mediaCodec.stop()
        mediaCodec.release()
        mediaMuxer.stop()
        mediaMuxer.release()
    }.start()
}
  1. 真正的读取数据
ini 复制代码
private fun encodeAudio() {
    bufferIndex = mediaCodec.dequeueInputBuffer(waitTime)
    if (bufferIndex >= 0) {
        val byteBuffer: ByteBuffer = mediaCodec.getInputBuffer(bufferIndex)?:throw Exception("byteBuffer is empty")
        byteBuffer.clear()
        len = audioRecord.read(byteBuffer, bufferSizeInBytes)
        val presentationTimeUs: Long = ( System.nanoTime() - nanoTime) / 1000
        if (len == AudioRecord.ERROR_INVALID_OPERATION
            || len == AudioRecord.ERROR_BAD_VALUE
        ) {
            Log.e(
               "TAG",
                "An error occured with the AudioRecord API !"
            )
        } else {
            mediaCodec.queueInputBuffer(bufferIndex, 0, len, presentationTimeUs, 0)
        }
    }
    val videoBufferInfo = MediaCodec.BufferInfo()
    var videobufferindex: Int = mediaCodec.dequeueOutputBuffer(videoBufferInfo, waitTime)
    if (videobufferindex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        //添加轨道
        audioTrackIndex = mediaMuxer.addTrack(mediaCodec.outputFormat)
        mediaMuxer.start()
    } else {
        while (videobufferindex >= 0 && isRecord) {
            //获取输出数据成功
            val videoOutputBuffer: ByteBuffer =
                mediaCodec.getOutputBuffer(videobufferindex)!!
            mediaMuxer.writeSampleData(audioTrackIndex, videoOutputBuffer, videoBufferInfo)
            mediaCodec.releaseOutputBuffer(videobufferindex, false)
            videobufferindex = mediaCodec.dequeueOutputBuffer(videoBufferInfo, 0)
        }
    }
}
  1. 停止编码,释放资源
scss 复制代码
fun stop(){
    isRecord = false
}

Thread {
    while (isRecord){
        encodeAudio()
    }
    audioRecord.stop()
    audioRecord.release()
    mediaCodec.stop()
    mediaCodec.release()
    mediaMuxer.stop()
    mediaMuxer.release()
}.start()

整体下来还是挺简单的,不过要注意的是,采样率、声道配置、音频数据格式 这些一定要设置好,后面解码的时候也要设置一样的,这样才可以正常播放。

用到的一些API

AudioRecord

java 复制代码
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
        int bufferSizeInBytes)
  1. audioSource - 音频源 这个参数表示从哪里获取音频数据。例如,使用麦克风作为音频源,可以传入 MediaRecorder.AudioSource.MIC 常量。除了麦克风之外,还可以使用其他音频源,例如电话输出、语音识别等。具体可参考MediaRecorder类中的定义。

  2. sampleRateInHz - 采样率 这个参数指定录制音频数据所采取的样本频率,即每秒钟采集多少次音频数据。通常情况下,CD质量的音频采样率是44100Hz,而电话质量的音频采样率是8000Hz。

  3. channelConfig - 声道配置 这个参数用于设置音频数据的声道数目。如果是单声道,则传入 AudioFormat.CHANNEL_IN_MONO 常量;如果是立体声,则传入 AudioFormat.CHANNEL_IN_STEREO 常量。

  4. audioFormat - 音频数据格式 这个参数指定音频数据的存储格式。通常情况下,使用16位的PCM编码(即 AudioFormat.ENCODING_PCM_16BIT)即可满足需求。除此之外,还有一些压缩格式,例如 AMR和 AAC等。

  5. bufferSizeInBytes - 缓存大小 这个参数指定内部音频缓存区的大小,以字节为单位。具体大小需要根据采样率、声道数以及编码格式来计算。

编码器中 MediaFormat.KEY_AAC_PROFILE的设置

KEY_AAC_PROFILE 的值代表了 AAC 编码的配置文件(Profile)。以下是一些常见的 KEY_AAC_PROFILE 对应的值:

  • MediaCodecInfo.CodecProfileLevel.AACObjectMain:主配置文件(Main Profile)。
  • MediaCodecInfo.CodecProfileLevel.AACObjectLC:低复杂度配置文件(Low Complexity Profile),也是最常用的配置文件。
  • MediaCodecInfo.CodecProfileLevel.AACObjectSSR:可扩展采样率(Scalable Sample Rate)配置文件。
  • MediaCodecInfo.CodecProfileLevel.AACObjectLTP:长时预测(Long Term Prediction)配置文件。
  • MediaCodecInfo.CodecProfileLevel.AACObjectHE:高效率配置文件(High Efficiency Profile)。
  • MediaCodecInfo.CodecProfileLevel.AACObjectELD:Enhanced Low Delay 配置文件。

这里列出的是一些常见的 AAC 音频编码配置文件,但具体支持的配置文件取决于设备和系统中可用的 AAC 编码器。在使用 MediaFormat.KEY_AAC_PROFILE 参数时,需要根据目标平台和目标设备的支持情况来选择合适的配置文件。

应注意,不同的配置文件可能支持不同的功能和性能特性,例如低延迟、高效率或更高音质,因此在选择配置文件时需根据实际需求进行权衡和选择。

总结

  • 今天就简单介绍了音频编码的实现,当然也可以使用FFMpeg来实现音视频的编解码,这部分的内容放到后面再做介绍了
相关推荐
关键帧Keyframe2 天前
音视频面试题集锦第 7 期
音视频开发·视频编码·客户端
关键帧Keyframe2 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端
蚝油菜花7 天前
MimicTalk:字节跳动和浙江大学联合推出 15 分钟生成 3D 说话人脸视频的生成模型
人工智能·开源·音视频开发
音视频牛哥9 天前
Android平台RTSP|RTMP播放器高效率如何回调YUV或RGB数据?
音视频开发·视频编码·直播
<Sunny>11 天前
MPP音视频总结
音视频开发·1024程序员节·海思mpp
GoFly开发者11 天前
GoFly快速开发框架已集成了RTSP流媒体服务器(直播、录播)代码插件-开发者集成流媒体服务器更加方便
go·音视频开发
音视频牛哥1 个月前
如何设计开发RTSP直播播放器?
音视频开发·视频编码·直播
dvlinker1 个月前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
音视频牛哥1 个月前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播