一、ffmpeg录制音频为pcm文件

前言

在qt中实现ffmpeg通过外接设备录制音频,因c语言相关代码执行步骤较为复杂,于是做此记录。

ffmpeg系列博客会陆续记录下来。


测试环境:

  • ffmpeg的shared版本
  • windows环境
  • qt5.12

使用ffmpeg首先需要下载ffmpeg相关库,并将其配置到环境变量和导入编译器中,这里下载shared版本(这样既有ffmpeg的可执行程序,又有ffmpoeg库文件)

链接:https://pan.baidu.com/s/1evRVXguim6U6b-Eo-_Ta_w?pwd=ld4k
提取码:ld4k
–来自百度网盘超级会员V3的分享

导入环境变量

只需将D:\1c++\ffmpeg-N-103922-g3ee4502753-win64-lgpl-shared\bin\路径添加到环境变量即可,这里视自己的路径而定,路径不能有中文(这里不细给出配置环境变量的步骤了,配置环境变量是基础)

image-20220619212945327

在cmd验证命令是否生效(配置完环境变量可能需要重启一下电脑)

image-20220619213101411

注意:一定要下载ffmpeg的shared版本,因为有些信息我们需要在命令行执行ffmpeg的相关命令去查才行


通过外接设备录制音频思路图:

image-20220613144239357

1、导入库.pro文件(静态库),动态库放入可执行程序下

.pro里加入的是静态库(即lib下的文件)和include下的头文件。bin下的dll为动态库文件,只需将动态库复制到自己的可执行程序下即可使用。lib下有mingw编译器的库,还有msvc编译器的库,不过.pro文件中库的写法是一致的

image-20220619205727481

.pro文件下

FFMPEG_HOME = D:/1c++/ffmpeg-N-103922-g3ee4502753-win64-lgpl-shared
INCLUDEPATH += $${
    
    FFMPEG_HOME}/include
LIBS += -L$${
    
    FFMPEG_HOME}/lib \
    -lavcodec \
    -lavdevice \
    -lavfilter \
    -lavformat \
    -lavutil \
    -lswscale \
    -lswresample

相应静态库的介绍

libavcodec: 用于各种类型声音、图像编解码
libavdevice: 用于音视频数据采集和渲染等功能的设备相关;
libavfileter:包含多媒体处理常用的滤镜功能;
libavformat:包含多种多媒体容器格式的封装、解封装工具;
libavutil:包含一些公共的工具函数
libpostproc: 用于后期效果处理
libswresample: 用于音频充采用和格式转换等功能;
libswscale: 用于食品场景比例缩放、色彩映射转换;

2、头文件

extern "C"{
    
    
// 设备相关API
#include <libavdevice/avdevice.h>
// 格式相关API
#include <libavformat/avformat.h>
// 工具相关API(比如错误处理)
#include <libavutil/avutil.h>
// 编码相关API
#include <libavcodec/avcodec.h>
}

3、注册设备,只需一次,可在主函数中注册

// 初始化libavdevice并注册所有输入和输出设备
    avdevice_register_all();

4、获取输入对象格式

//short_name在windows中是dshow
const AVInputFormat *av_find_input_format(const char *short_name);

cmd通过以下命令查看没设备格式

ffmpeg -hide_banner -devices	---获取所有设备dshow

image-20220619145804533

注意:demuxer和muxer,上图D是demuxer,muxer是E,而dshow是demuxer,后面cmd中有些命令要用到demuxer

5、打开录音设备

cmd命令查看设备名称

ffmpeg -hide_banner -f dshow -list_devices true -i dummy

image-20220619145707633

打开设备

//ps为,url为设备名称,fmtav_find_input_format()方法的返回值,options可以为空
int avformat_open_input(AVFormatContext **ps, const char *url,
                        const AVInputFormat *fmt, AVDictionary **options);
//必须打开设备之后必须avformat_close_input()关闭设备

6、采集数据

//s为为打开设备方法的第一个参数ps,pkt为结构体,由av_packet_alloc()方法进行初始化,其中调用av_packet_alloc()方法之后必须调用av_packet_free()释放结构体
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
//数据都将被采集到pkt结构体中

通过以下方法,将采集到的音频数据写到文件中

file.write((char *)pkt->data,pkt->size);

注意:到这里为止,很多方法和参数都需要查阅源码,比如AVPacket结构体内部的变量

7、关闭设备

在关闭设备之前,时刻注意开启了哪些,这个时候需要考虑哪些都应被关闭掉

//本质上是关闭格式内容,应填入打开设备时的第一个参数
void avformat_close_input(AVFormatContext **s);

完整代码:(我这里是将功能封装在线程里,其中还考虑了线程的关闭问题)

AudioThread.h

#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H

#include <QObject>
#include <QThread>

class AudioThread : public QThread
{
    
    
    Q_OBJECT
public:
    explicit AudioThread(QObject *parent = nullptr);
    ~AudioThread();

private:
    void run() override;
signals:

};

#endif // AUDIOTHREAD_H

AudioThread.cpp

#include "audiothread.h"

#include <QDebug>
#include <QFile>

//ffmpeg库为纯C语言书写,引用C语言的头文件
extern "C"{
    
    
// 设备相关API
#include <libavdevice/avdevice.h>
// 格式相关API
#include <libavformat/avformat.h>
// 工具相关API(比如错误处理)
#include <libavutil/avutil.h>
// 编码相关API
#include <libavcodec/avcodec.h>
}

#ifdef Q_OS_WIN
    // PCM文件的文件名
    #define FILENAME "E:/media/out.pcm"
#else
    #define FILENAME "/Users/mj/Desktop/out.pcm"
#endif

AudioThread::AudioThread(QObject *parent) : QThread(parent)
{
    
    
    // 当监听到线程结束时(finished),就调用deleteLater回收内存
    connect(this,&AudioThread::finished,this,[=](){
    
    
        this->deleteLater();
        qDebug()<<"线程结束";
    });
}

AudioThread::~AudioThread()
{
    
    
    //强制关闭窗口时,线程也能安全关闭
    requestInterruption();
    wait();
    qDebug()<<"析构函数";
}

void AudioThread::run()
{
    
    

    char *FMT_NAME="dshow";
    const AVInputFormat *localAv_find_input_format = av_find_input_format(FMT_NAME);
    if(!localAv_find_input_format){
    
    
        qDebug()<<"找不到输入格式"<<FMT_NAME;
        return;
    }
    qDebug()<<FMT_NAME;
    AVFormatContext *ctx=nullptr;
    char *device_name="audio=麦克风 (Realtek High Definition Audio)";
    int ret = avformat_open_input(&ctx,device_name,localAv_find_input_format,nullptr);
    if(ret!=0){
    
    
        char errbuf[1024];
        av_strerror(ret,errbuf,sizeof(errbuf));
        qDebug()<<"打开设备失败"<<ret<<errbuf;
        //关闭设备
        avformat_close_input(&ctx);
        return;
    }

    QFile file(FILENAME);
    if(!file.open(QIODevice::WriteOnly)){
    
    
        qDebug()<<"文件打开失败"<<FILENAME;
        avformat_close_input(&ctx);
        return;
    }

    AVPacket *pkt = av_packet_alloc();
    while(!isInterruptionRequested()){
    
    
        //采集数据
        ret = av_read_frame(ctx,pkt);

        if(ret==0){
    
    
            //向文件中写
            file.write((char *)pkt->data,pkt->size);
            qDebug()<<"正在录制,文件正在写入";

            //释放资源
            av_packet_unref(pkt);
        }else if (ret == AVERROR(EAGAIN)) {
    
     // 资源临时不可用
            continue;
        }
        else{
    
    
            char errbuf[1024];
            av_strerror(ret,errbuf,sizeof(errbuf));
            qDebug()<<"av_read_frame出错"<<errbuf<<sizeof (errbuf);
            return;
        }
        av_packet_unref(pkt);
    }

    file.close();
    //释放资源
    av_packet_free(&pkt);
    //关闭设备
    avformat_close_input(&ctx);
    qDebug()<<"录制成功结束";

    return;
}

注意:该代码对可能出现的问题都进行了一定的优化,读者使用时只需关注关键代码


线程调用

void MainWindow::on_pushButton_clicked()
{
    
    
    if(!is_record){
    
    
        audio_thread=new AudioThread(this);
        audio_thread->start();
        connect(audio_thread,&AudioThread::finished,audio_thread,[=](){
    
    
            ui->pushButton->setText("开始录制");
            is_record=false;
            if(is_abnormal){
    
    
                qDebug()<<"线程异常结束";
            }
        });
        qDebug()<<"开始录制";
        ui->pushButton->setText("结束录制");
        is_record=true;
    }else{
    
    
        is_abnormal=false;
        audio_thread->requestInterruption();
        audio_thread->wait();
        audio_thread=nullptr;
        qDebug()<<"结束录制";
        ui->pushButton->setText("开始录制");
        is_record=false;
    }
}

注意:.h文件中提前声明了以下全局变量

	AudioThread *audio_thread=nullptr;
    bool is_record=false;
    bool is_abnormal=true;

注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用


码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方

猜你喜欢

转载自blog.csdn.net/weixin_44092851/article/details/125371975