10

JavaCV FFmpeg采集麦克风PCM音频数据

 3 years ago
source link: http://www.cnblogs.com/itqn/p/14224295.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前阵子用一个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

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

6reeM3E.png!mobile

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

2. 利用FFmpeg解码

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

zqqyErQ.png!mobile

可以看出除了解码函数,音频解码流程和视频解码流程是一致的,音频解码调用的是 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”退出:

uURvqaU.png!mobile

当然如果不用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” 获取。

BzIjYnv.jpg!mobile关注公众号 ,阅读更多文章。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK