前阵子用一个JavaCV的FFmpeg库实现了YUV视频数据的采集。

同样的采集PCM音频数据也可以采用JavaCV的FFmpeg库。

使用的依赖为 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格式数据,将数据写入文件。

其实音频的解码过程跟视频的解码过程是几乎一致的,可以看出除了解码函数,音频解码流程和视频解码流程是一致的。

音频解码调用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();

打开音频流

ffmpeg通过avformat_open_input()来打开音频流。指定format为dshow

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;    }}

打开解码器

可以通过音频流来查找解码器,然后打开对应的解码器,对音频流进行解码。

实现代码如下:

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) &gt;= 0 && pkt.stream_index() == audioIdx) {        ret = avcodec_decode_audio4(pCodecCtx, pFrame, got, pkt);        if (ret &lt; 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 &lt; 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 &lt; buff.length) {            break;        }        player.play(buff);    }    is.close();    player.release();}