前言
在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\路径添加到环境变量即可,这里视自己的路径而定,路径不能有中文(这里不细给出配置环境变量的步骤了,配置环境变量是基础)
在cmd验证命令是否生效(配置完环境变量可能需要重启一下电脑)
注意:一定要下载ffmpeg的shared版本,因为有些信息我们需要在命令行执行ffmpeg的相关命令去查才行
通过外接设备录制音频思路图:
1、导入库.pro文件(静态库),动态库放入可执行程序下
.pro里加入的是静态库(即lib下的文件)和include下的头文件。bin下的dll为动态库文件,只需将动态库复制到自己的可执行程序下即可使用。lib下有mingw编译器的库,还有msvc编译器的库,不过.pro文件中库的写法是一致的
.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
注意:demuxer和muxer,上图D是demuxer,muxer是E,而dshow是demuxer,后面cmd中有些命令要用到demuxer
5、打开录音设备
cmd命令查看设备名称
ffmpeg -hide_banner -f dshow -list_devices true -i dummy
打开设备
//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;
注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用
码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方