1. 程式人生 > >JavaCV FFmpeg採集麥克風PCM音訊資料

JavaCV FFmpeg採集麥克風PCM音訊資料

前陣子用一個JavaCV的FFmpeg庫實現了YUV視訊資料地採集,同樣的採集PCM音訊資料也可以採用JavaCV的FFmpeg庫。 [傳送門:JavaCV FFmpeg採集攝像頭YUV資料](https://www.cnblogs.com/itqn/p/13789079.html) 首先引入 javacpp-ffmpeg依賴: ```xml ``` ##### 1. 查詢麥克風裝置 要採集麥克風的PCM資料,首先得知道麥克風的裝置名稱,可以通過FFmpeg來查詢麥克風裝置。 ```shell ffmpeg.exe -list_devices true -f dshow -i dummy ``` 在我的電腦上結果顯示如下: ![](https://img2020.cnblogs.com/blog/2083963/202101/2083963-20210102220010942-729193803.png) 其中 “麥克風陣列 (Realtek(R) Audio)” 就是麥克風的裝置名稱。(這裡建議用耳麥[External Mic (Realtek(R) Audio)]錄製,質量要好很多很多) ##### 2. 利用FFmpeg解碼 採集麥克風資料即將麥克風作為音訊流輸入,通過FFmpeg解碼獲取音訊幀,然後將視訊幀轉為PCM格式,最後將資料寫入檔案即可,其實音訊的解碼過程跟視訊的解碼過程是幾乎一致的,下面是FFmpeg音訊的解碼流程: ![](https://img2020.cnblogs.com/blog/2083963/202101/2083963-20210103140809773-1121898211.png) 可以看出除了解碼函式,音訊解碼流程和視訊解碼流程是一致的,音訊解碼呼叫的是`avcodec_decode_audio4`,而視訊解碼呼叫的是`avcodec_decode_video2`。 ##### 3. 開發音訊幀採集器 根據FFmpeg的解碼流程,實現音訊幀採集器大概需要經過以下幾個步驟: > FFmpeg初始化 首先需要使用`av_register_all()`這個函式完成編碼器和解碼器的初始化,只有初始化了編碼器和解碼器才能正常使用;另外要採集的是裝置,所以還需要呼叫`avdevice_register_all()`完成初始化。 > 分配AVFormatContext 接著需要分配一個AVFormatContext,可以通過`avformat_alloc_context()`來分配AVFormatContext。 ```java pFormatCtx = avformat_alloc_context(); ``` > 開啟音訊流 通過`avformat_open_input()`來開啟音訊流,這裡需要注意的是input format要指定為`dshow`,可以通過`av_find_input_format("dshow")`獲取AVInputFormat物件。 ```java ret = avformat_open_input(pFormatCtx, String.format("audio=%s", input), av_find_input_format("dshow"), (AVDictionary) null); ``` 注意:這裡是音訊用的是audio,不是video。 > 查詢音訊流 需要注意的是,查詢音訊流之前需要呼叫`avformat_find_stream_info()`,下面是查詢視音訊的程式碼: ```java 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程式碼如下: ```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 解碼器開啟失敗"); } ``` > 採集音訊幀 最後就是採集音訊幀了,這裡需要注意的是,如果向採集麥克風的音訊流解碼得到的是自己想要的格式,需要再次進行格式轉化。 ```java 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中: ```java 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播放,命令如下: ```shell ffplay.exe -ar 44100 -ac 2 -f s16le -i s16.pcm ``` 播放的時候可以按“Q”退出: ![](https://img2020.cnblogs.com/blog/2083963/202101/2083963-20210103150049414-660432360.png) 當然如果不用ffplay來播放pcm,也可以自己寫java程式來播放: ```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青年](https://images.cnblogs.com/cnblogs_com/itqn/1797476/o_200702010008qrcode.jpg) **關注公眾號**,閱讀更多