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青年
    关注公众号,阅读更多文章。

  • 相关阅读:
    腰围2尺1,2,3,4,5,6,7,8寸各自等于是多少厘米/英寸(对比表)
    Android开发模板------自己定义SimpleCursorAdapter的使用
    怎样在多线程中使用JNI?
    UVa753/POJ1087_A Plug for UNIX(网络流最大流)(小白书图论专题)
    图解iPhone开发新手教程
    Why Hadoop2
    读完了csapp(中文名:深入理解计算机系统)
    hadoop备战:一台x86计算机搭建hadoop的全分布式集群
    Win7 公布网站 HTTP 错误 404.4
    Dump 文件生成与分析
  • 原文地址:https://www.cnblogs.com/itqn/p/14224295.html
Copyright © 2011-2022 走看看