zoukankan      html  css  js  c++  java
  • JavaCV FFmpeg采集麦克风PCM音频数据

    前阵子用一个JavaCV的FFmpeg库实现了YUV视频数据地采集,同样的采集PCM音频数据也可以采用JavaCV的FFmpeg库。

    传送门:JavaCV FFmpeg采集摄像头YUV数据

    首先引入 javacpp-ffmpeg依赖:

    <dependency>
        <groupId>org.bytedeco.javacpp-presets</groupId>
        <artifactId>ffmpeg</artifactId>
        <version>${ffmpeg.version}</version>
    </dependency>
    
    1. 查找麦克风设备

    要采集麦克风的PCM数据,首先得知道麦克风的设备名称,可以通过FFmpeg来查找麦克风设备。

    ffmpeg.exe -list_devices true -f dshow -i dummy  
    

    在我的电脑上结果显示如下:

    其中 “麦克风阵列 (Realtek(R) Audio)” 就是麦克风的设备名称。(这里建议用耳麦[External Mic (Realtek(R) Audio)]录制,质量要好很多很多)

    2. 利用FFmpeg解码

    采集麦克风数据即将麦克风作为音频流输入,通过FFmpeg解码获取音频帧,然后将视频帧转为PCM格式,最后将数据写入文件即可,其实音频的解码过程跟视频的解码过程是几乎一致的,下面是FFmpeg音频的解码流程:

    可以看出除了解码函数,音频解码流程和视频解码流程是一致的,音频解码调用的是avcodec_decode_audio4,而视频解码调用的是avcodec_decode_video2

    3. 开发音频帧采集器

    根据FFmpeg的解码流程,实现音频帧采集器大概需要经过以下几个步骤:

    FFmpeg初始化

    首先需要使用av_register_all()这个函数完成编码器和解码器的初始化,只有初始化了编码器和解码器才能正常使用;另外要采集的是设备,所以还需要调用avdevice_register_all()完成初始化。

    分配AVFormatContext

    接着需要分配一个AVFormatContext,可以通过avformat_alloc_context()来分配AVFormatContext。

    pFormatCtx = avformat_alloc_context();
    

    打开音频流

    通过avformat_open_input()来打开音频流,这里需要注意的是input format要指定为dshow,可以通过av_find_input_format("dshow")获取AVInputFormat对象。

    ret = avformat_open_input(pFormatCtx, String.format("audio=%s", input), av_find_input_format("dshow"), (AVDictionary) null);
    

    注意:这里是音频用的是audio,不是video。

    查找音频流

    需要注意的是,查找音频流之前需要调用avformat_find_stream_info(),下面是查找视音频的代码:

    ret = avformat_find_stream_info(pFormatCtx, (AVDictionary) null);
    for (int i = 0; i < pFormatCtx.nb_streams(); i++) {
        if (pFormatCtx.streams(i).codec().codec_type() == AVMEDIA_TYPE_AUDIO) {
            audioIdx = i;
            break;
        }
    }
    

    打开解码器

    可以通过音频流来查找解码器,然后打开解码器,对音频流进行解码,Java代码如下:

    pCodecCtx = pFormatCtx.streams(audioIdx).codec();
    pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
    if (pCodec == null) {
        throw new FFmpegException("没有找到合适的解码器:" + pCodecCtx.codec_id());
    }
    // 打开解码器
    ret = avcodec_open2(pCodecCtx, pCodec, (AVDictionary) null);
    if (ret != 0) {
        throw new FFmpegException(ret, "avcodec_open2 解码器打开失败");
    }
    

    采集音频帧

    最后就是采集音频帧了,这里需要注意的是,如果向采集麦克风的音频流解码得到的是自己想要的格式,需要再次进行格式转化。

    public AVFrame grab() throws FFmpegException {
        if (av_read_frame(pFormatCtx, pkt) >= 0 && pkt.stream_index() == audioIdx) {
            ret = avcodec_decode_audio4(pCodecCtx, pFrame, got, pkt);
            if (ret < 0) {
                throw new FFmpegException(ret, "avcodec_decode_audio4 解码失败");
            }
            if (got[0] != 0) {
                return pFrame;
            }
            av_packet_unref(pkt);
        }
        return null;
    }
    
    4. 将音频帧数据写入文件

    通过音频解码之后可以得到PCM数据,这里为了读取方便,我将音频数据转化为AV_SAMPLE_FMT_S16,即LRLRLR这种格式,而不是planar,这样子读取PCM数据的时候,只需要读取data[0]即可,下面是一段采集主程序,将采集的音频pcm数据写入到s16.pcm中:

    public static void main(String[] args) throws FFmpegException, IOException {
        FFmpegRegister.register();
        // 耳机的麦克风质量要好得多
        AudioGrabber a = AudioGrabber.create("External Mic (Realtek(R) Audio)");
        // AV_SAMPLE_FMT_S16
        AudioPCMWriter writer = null;
        for (int i = 0; i < 100; i++) {
            AVFrame f = a.grab();
            if (writer == null) {
                writer = AudioPCMWriter.create(new File("s16.pcm"), toChannelLayout(a.channels()), a.sample_fmt(), a.sample_rate(),
                    toChannelLayout(a.channels()), AV_SAMPLE_FMT_S16, a.sample_rate(), f.nb_samples());
            }
            writer.write(f);
        }
        writer.release();
        a.release();
    }
    
    5. 播放采集的pcm数据

    采集的pcm数据可以通过ffplay播放,命令如下:

    ffplay.exe -ar 44100 -ac 2 -f s16le -i s16.pcm
    

    播放的时候可以按“Q”退出:

    当然如果不用ffplay来播放pcm,也可以自己写java程序来播放:

    public static void main(String[] args) throws IOException, LineUnavailableException {
        AudioPCMPlayer player = AudioPCMPlayer.create(2, AudioUtils.toBit(AV_SAMPLE_FMT_S16), 44100);
        InputStream is = new FileInputStream("s16.pcm");
        byte[] buff = new byte[4096];
        int ret = -1;
        while ((ret = is.read(buff)) != -1) {
            if (ret < buff.length) {
                break;
            }
            player.play(buff);
        }
        is.close();
        player.release();
    }
    

    =========================================================
    音频帧采集器、及pcm播放程序源码可关注公众号 “HiIT青年” 发送 “ffmpeg-pcm” 获取。

    HiIT青年
    关注公众号,阅读更多文章。

  • 相关阅读:
    171 01 Android 零基础入门 03 Java常用工具类02 Java包装类 01 包装类简介 01 Java包装类内容简介
    170 01 Android 零基础入门 03 Java常用工具类01 Java异常 08 Java异常总结 01 异常总结
    169 01 Android 零基础入门 03 Java常用工具类01 Java异常 07 异常链 01 异常链简介
    168 01 Android 零基础入门 03 Java常用工具类01 Java异常 06 自定义异常 01 自定义异常类
    167 01 Android 零基础入门 03 Java常用工具类01 Java异常 05 使用throw和throws实现异常处理 02 使用throw抛出异常对象
    166 01 Android 零基础入门 03 Java常用工具类01 Java异常 05 使用throw和throws实现异常处理 01 使用throws声明异常类型
    165 01 Android 零基础入门 03 Java常用工具类01 Java异常 04 使用try…catch…finally实现异常处理 05 return关键字在异常处理中的使用
    DevExpress WPF v20.2版本亮点放送:全新升级的PDF Viewer
    界面控件DevExpress使用教程:Dashboard – 自定义导出
    DevExpress WinForms帮助文档:表单控件
  • 原文地址:https://www.cnblogs.com/itqn/p/14224295.html
Copyright © 2011-2022 走看看