Android如何将采集到的音频PCM文件转为WAV并保存

1.Android音频采集

添加权限

<uses-permission android:name="android.permission.RECORD_AUDIO" />

动态申请权限

PermissionX是一个用于处理Android运行时权限的框架。它的目的是简化和标准化处理运行时权限的申请、拒绝和永久拒绝等操作,让开发者可以更轻松地管理权限。

PermissionX通过在每个系统版本上进行额外的适配,确保了对于不同版本的Android系统都能进行有效的权限处理。例如,从Android 13开始,READ_EXTERNAL_STORAGE权限会完全失去作用,申请它不会产生任何效果。PermissionX通过特殊处理这个权限,使其在特定版本的系统上仍然能够被申请和使用。

使用PermissionX框架,开发者可以方便地请求权限,并对用户的拒绝或永久拒绝做出适当的响应。例如,当权限被用户拒绝时,PermissionX可以提醒用户,并在必要时引导用户手动开启权限;对于一些特殊权限,PermissionX也提供了特殊的处理方式。

引入PermissionX框架到项目中,只需要将其加入到依赖库中,并调用相应的初始化方法。例如,在Java代码中,可以通过调用init()方法来进行初始化,并在初始化的时候传入一个FragmentActivity参数。之后,就可以使用PermissionX提供的方法来请求权限了。

引入权限申请库

implementation 'com.permissionx.guolindev:permissionx:1.4.0'

申请权限的部分代码

PermissionX.init(this).permissions(Manifest.permission.RECORD_AUDIO)
            .request { _, _, _ -> 
    //TODO 申请成功之后的代码写在这里
 }

初始化AudioRecorder

AudioRecorder是Android平台上的一个音频采集API,它基于AudioRecorder的API(最终会创建AudioRecord用于与AudioFlinger进行交互),能直接将采集到的音频数据转化为执行的编码格式,并保存。使用AudioRecorder可以很方便地进行音频的捕获和视频的捕获,系统封装也很好,便于使用。然而,使用AudioRecorder录下来的音频是经过编码的,没有办法得到原始的音频。

同时,AudioRecorder采集到的音频是经过编码的,相比于使用AudioRecord进行音频采集,这种方式采集到的音频数据不够灵活。AudioRecord可以获取到一帧帧PCM数据,之后可以对这些数据进行处理,对于需要进行音频处理的开发者来说,使用AudioRecord可能会更加合适。

需要设置的参数有:音频源,采样率,声道数,数据类型,最小缓冲区

最小缓冲区的大小用AudioRecord.getMinBufferSize接口,根据采样率,声道数,数据类型返回该值;

private const val SAMPLE_RATE_IN_HZ_16K = 16000
private var minBufferSize = 1280
private var audioRecord: AudioRecord? = null
private var captureThread: Thread? = null
private var isRunning = false
private var isFloats = false

fun start(isFloats: Boolean) {
    this.isFloats = isFloats
    minBufferSize = AudioRecord.getMinBufferSize(
        SAMPLE_RATE_IN_HZ_16K,
        AudioFormat.CHANNEL_IN_MONO,
        if (isFloats) AudioFormat.ENCODING_PCM_FLOAT else AudioFormat.ENCODING_PCM_16BIT
    )
    audioRecord = AudioRecord(
        MediaRecorder.AudioSource.VOICE_COMMUNICATION,
        SAMPLE_RATE_IN_HZ_16K,
        AudioFormat.CHANNEL_IN_MONO,
        AudioFormat.ENCODING_PCM_FLOAT,
        minBufferSize
    )
    audioRecord!!.startRecording()
    isRunning = true
}

音频采集时可以设置的格式有

public static final int ENCODING_DEFAULT = 1;
// These values must be kept in sync with core/jni/android_
// Also sync av/services/audiopolicy/managerdefault/ConfigP
/** Audio data format: PCM 16 bit per sample. Guaranteed to
public static final int ENCODING_PCM_16BIT = 2;
/** Audio data format: PCM 8 bit per sample. Not guaranteed
public static final int ENCODING_PCM_8BIT = 3;
/** Audio data format: single-precision floating-point per 
public static final int ENCODING_PCM_FLOAT = 4;
/** Audio data format: AC-3 compressed, also known as Dolby
public static final int ENCODING_AC3 = 5;
/** Audio data format: E-AC-3 compressed, also known as Dol
public static final int ENCODING_E_AC3 = 6;
    /** Audio data format: DTS compressed */
public static final int ENCODING_DTS = 7;
    /** Audio data format: DTS HD compressed */
public static final int ENCODING_DTS_HD = 8;
    /** Audio data format: MP3 compressed */
public static final int ENCODING_MP3 = 9;
    /** Audio data format: AAC LC compressed */
public static final int ENCODING_AAC_LC = 10;
    /** Audio data format: AAC HE V1 compressed */
public static final int ENCODING_AAC_HE_V1 = 11;
    /** Audio data format: AAC HE V2 compressed */
public static final int ENCODING_AAC_HE_V2 = 12;

开始采集数据

fun capture(listener: OnReadListener) {
    captureThread = Thread {
        while (isRunning) {
            var nReadBytes: Int
            if (isFloats) {
                val data = FloatArray(minBufferSize)
                nReadBytes =
                    audioRecord!!.read(data, 0, minBufferSize, AudioRecord.READ_BLOCKING)
                listener.read(nReadBytes, data)
            } else {
                val data = ByteArray(minBufferSize)
                nReadBytes =
                    audioRecord!!.read(data, 0, minBufferSize)
                listener.read(nReadBytes, data)
            }
        }
    }
    captureThread!!.start()
}

数据回调接口

interface OnReadListener {
    fun read(readSize: Int, floatArray: FloatArray)
    fun read(readSize: Int, byteArray: ByteArray)
}

结束采集,关闭AudioRecorder

fun stop() {
    isRunning = false
    captureThread!!.interrupt()
    try {
        audioRecord!!.stop()
        audioRecord!!.release()
    } catch (e : Exception) {
        e.printStackTrace()
    }
}

采集到的数据后将PCM数据保存

AudioCapture.capture(object : AudioCapture.OnReadListener {
    override fun read(readSize: Int, floatArray: FloatArray) {
        val savePath = getDir(
            "spanner", Context.MODE_PRIVATE
        ).absolutePath + File.separator + "/Music/"
        //保存浮点数据到本地文件
        SaveBytesAsFile.writeFile(floatArray, savePath + "record.pcm")
    }
    override fun read(readSize: Int, byteArray: ByteArray) {
        
    }
})



public class SaveBytesAsFile {

    public SaveBytesAsFile() {
    }

    public void writeFile(byte[] bytes) {
        long time = System.currentTimeMillis();
        String path = Environment.getExternalStorageDirectory() + File.separator + "g711ToAAC.aac";
        try {
            FileOutputStream out = new FileOutputStream(path, true);//指定写到哪个路径中
            FileChannel fileChannel = out.getChannel();
            fileChannel.write(ByteBuffer.wrap(bytes)); //将字节流写入文件中
            fileChannel.force(true);//强制刷新
            fileChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 将流写入口自定义路径
     *
     * @param bytes
     * @param path  ps:快捷回复AAC转PCM之后写入到指定路径   请勿修改
     */
    public static void writeFile(byte[] bytes, String path) {

        try {
            FileOutputStream out = new FileOutputStream(path, true);//指定写到哪个路径中
            FileChannel fileChannel = out.getChannel();
            fileChannel.write(ByteBuffer.wrap(bytes)); //将字节流写入文件中
            fileChannel.force(true);//强制刷新
            fileChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void writeFile(float[] floats, String path) {
        try {
            FileOutputStream fos = new FileOutputStream(path, true);
            DataOutputStream dos = new DataOutputStream(fos);
            for (float f : floats) {
                dos.writeFloat(f);
            }
            dos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.PCM文件转WAV

FFPlay是FFmpeg工程自带的简单播放器,它使用FFmpeg提供的解码器和SDL库进行视频播放。

FFPlay在播放一个互联网上的视频文件时,会经过解协议、解封装、解码视音频,以及视音频同步等步骤。如果播放的是本地文件,则不需要解协议步骤。

在视频播放过程中,FFPlay的主线程负责键盘消息处理以及图像渲染,并创建解复用线程read_thread。视频解码线程video_thread从video packets队列缓冲区读取视频包,解码后将视频帧放入video frame队列,供渲染线程使用。音频播放线程从audio frame队列获取解码后的音频包,如果需要进行格式转换后供给声卡播放。

播放浮点型音频的指令

ffplay -f f32be  -ar 16000 -ac 1 -i record.pcm

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它提供了录制、转换以及流化音视频的完整解决方案,包含了非常先进的音频/视频编解码库libavcodec。这个项目最早由Fabrice Bellard发起,2004年至2015年间由Michael Niedermayer主要负责维护。许多FFmpeg的开发人员都来自MPlayer项目,而且当前FFmpeg也是放在MPlayer项目组的服务器上。项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。FFmpeg编码库可以使用GPU加速。

使用FFMPEG

ffmpeg -f 音频格式 -ar 采样率 -ac 频道数 -i pcm文件名 -ar 采样率 -ac 频道数 wav文件名

此处把浮点型的PCM数据转化

//浮点型音频PCM转WAV格式音频指令
ffmpeg -f f32be -ar 16000 -ac 1 -i record.pcm -ar 16000 -ac 1 record.wav

查看ffmepg可以解析的格式指令

ffmpeg -formats

DE f32be           PCM 32-bit floating-point big-endian
DE f32le           PCM 32-bit floating-point little-endian
DE f64be           PCM 64-bit floating-point big-endian
DE f64le           PCM 64-bit floating-point little-endian
DE s16be           PCM signed 16-bit big-endian
DE s16le           PCM signed 16-bit little-endian
DE s24be           PCM signed 24-bit big-endian
DE s24le           PCM signed 24-bit little-endian
DE s32be           PCM signed 32-bit big-endian
DE s32le           PCM signed 32-bit little-endian
DE u16be           PCM unsigned 16-bit big-endian
DE u16le           PCM unsigned 16-bit little-endian
DE u24be           PCM unsigned 24-bit big-endian
DE u24le           PCM unsigned 24-bit little-endian
DE u32be           PCM unsigned 32-bit big-endian
DE u32le           PCM unsigned 32-bit little-endian

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会写代码的猴子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值