前陣子用一個JavaCV的FFmpeg庫實現了YUV視頻數據地採集,一樣的採集PCM音頻數據也能夠採用JavaCV的FFmpeg庫。html
傳送門:JavaCV FFmpeg採集攝像頭YUV數據java
首先引入 javacpp-ffmpeg依賴:shell
<dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>ffmpeg</artifactId> <version>${ffmpeg.version}</version> </dependency>
要採集麥克風的PCM數據,首先得知道麥克風的設備名稱,能夠經過FFmpeg來查找麥克風設備。ide
ffmpeg.exe -list_devices true -f dshow -i dummy
在個人電腦上結果顯示以下:
函數
其中 「麥克風陣列 (Realtek(R) Audio)」 就是麥克風的設備名稱。(這裏建議用耳麥[External Mic (Realtek(R) Audio)]錄製,質量要好不少不少)編碼
採集麥克風數據即將麥克風做爲音頻流輸入,經過FFmpeg解碼獲取音頻幀,而後將視頻幀轉爲PCM格式,最後將數據寫入文件便可,其實音頻的解碼過程跟視頻的解碼過程是幾乎一致的,下面是FFmpeg音頻的解碼流程:3d
能夠看出除了解碼函數,音頻解碼流程和視頻解碼流程是一致的,音頻解碼調用的是avcodec_decode_audio4
,而視頻解碼調用的是avcodec_decode_video2
。code
根據FFmpeg的解碼流程,實現音頻幀採集器大概須要通過如下幾個步驟:orm
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; }
經過音頻解碼以後能夠獲得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(); }
採集的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」 獲取。
關注公衆號,閱讀更多文章。