音视频之代码录制音频(六)
通过代码录音
权限申请
在Mac平台,有两个注意点: - 需要在Info.plist中添加麦克风的使用说明,申请麦克风的使用权限 - 使用Debug模式运行程序
上面两点非常重要,两个都会导致闪退
文件目录配置如下:
pro文件配置如下:
macx { FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.4_2 QMAKE_INFO_PLIST = mac/Info.plist }
相关库
使用代码录音,需要用到FFmpeg中库有4个,如下:
extern "C" { // 设备相关API #include <libavdevice/avdevice.h> // 格式相关API #include <libavformat/avformat.h> // 工具(比如错误处理) #include <libavutil/avutil.h> // 编码相关的API #include <libavcodec/avcodec.h> }
录音功能主要步骤
- 注册设备
-
// 注册设备 avdevice_register_all();
- 获取输入格式对象
-
// 获取输入格式对象 AVInputFormat *fmt = av_find_input_format(FMT_NAME);
- 打开设备
-
// 打开设备 int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr);
- 采集数据
-
// 不断采集数据 ret = av_read_frame(ctx, pkt);
- 释放资源
-
// 释放资源 av_packet_free(&pkt); // 关闭设备 avformat_close_input(&ctx);
核心代码
宏定义
Windows和Mac环境的格式名称、设备名称都是不同的,所以使用条件编译实现跨平台。
#ifdef Q_OS_WIN // 格式名称 #define FMT_NAME "dshow" // 设备名称 #define DEVICE_NAME "" // PCM文件名 #define FILEPATH "F:/" #else #define FMT_NAME "avfoundation" #define DEVICE_NAME ":0" #define FILEPATH "/Users/muzi/Desktop/" #endif
配置pro文件中FFmpeg运行环境
pro中文件配置如下所示:
win32 { FFMPEG_HOME = F:/test } macx { FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.4_2 QMAKE_INFO_PLIST = mac/Info.plist } INCLUDEPATH += $${FFMPEG_HOME}/include LIBS += -L $${FFMPEG_HOME}/lib \ -lavdevice \ -lavformat \ -lavcodec \ -lavutil
多线程
录音属于耗时操作,为了避免阻塞主线程,最好在子线程中进行录音操作。这里需要创建一个继承自QThread的线程类,线程一旦启动(start),就会自动调用run函数
.h中代码如下所示
#ifndef AUDIOTHREAD_H #define AUDIOTHREAD_H #include <QThread> class AudioThread : public QThread { Q_OBJECT private: void run(); bool _stop = false; public: explicit AudioThread(QObject *parent = nullptr); ~AudioThread(); void setStop(bool stop); signals: }; #endif // AUDIOTHREAD_H
cpp中代码如下所示
AudioThread::AudioThread(QObject *parent) : QThread(parent) { // 当监听到线程结束时(finished),就调用deleteLater回收内存 connect(this, &AudioThread::finished, this, &AudioThread::deleteLater); } /// 在析构函数中断开连接,安全退出线程 AudioThread::~AudioThread() { } // 当线程启动的时候(start),就会自动调用run函数 // run函数中的代码是在子线程中执行的 // 耗时操作应该放在run函数中 void AudioThread::run() { } void AudioThread::setStop(bool stop) { _stop = stop; }
具体代码实现
写入数据、关闭设备
// 当线程启动的时候(start),就会自动调用run函数 // run函数中的代码是在子线程中执行的 // 耗时操作应该放在run函数中 void AudioThread::run() { qDebug() << this << "开始执行----------"; // 获取输入格式对象 AVInputFormat *fmt = av_find_input_format(FMT_NAME); if (!fmt) { qDebug() << "获取输入格式对象失败" << FMT_NAME; return; } // 格式上下文(将来可以利用上下文操作设备) AVFormatContext *ctx = nullptr; // 打开设备 int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr); if (ret < 0) { char errbuf[1024]; av_strerror(ret, errbuf, sizeof (errbuf)); qDebug() << "打开设备失败" << errbuf; return; } // 打印一下录音设备的参数信息 showSpec(ctx); // 文件名 QString filename = FILEPATH; filename += QDateTime::currentDateTime().toString("MM_dd_HH_mm_ss"); filename += ".pcm"; QFile file(filename); // 打开文件 // WriteOnly:只写模式。如果文件不存在,就创建文件;如果文件存在,就会清空文件内容 if (!file.open(QFile::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((const char *) pkt->data, pkt->size); } else if (ret == AVERROR(EAGAIN)) { // 资源临时不可用 continue; } else { // 其他错误 char errbuf[1024]; av_strerror(ret, errbuf, sizeof (errbuf)); qDebug() << "av_read_frame error" << errbuf << ret; break; } // 必须要加,释放pkt内部的资源 av_packet_unref(pkt); } // 释放资源 // 关闭文件 file.close(); // 释放资源 av_packet_free(&pkt); // 关闭设备 avformat_close_input(&ctx); qDebug() << this << "正常结束----------"; }
获取录音设备的相关参数
void showSpec(AVFormatContext *ctx) { // 获取输入流 AVStream *stream = ctx->streams[0]; // 获取音频参数 AVCodecParameters *params = stream->codecpar; // 声道数 qDebug() << params->channels; // 采样率 qDebug() << params->sample_rate; // 采样格式 qDebug() << params->format; // 每一个样本的一个声道占用多少个字节 qDebug() << av_get_bytes_per_sample((AVSampleFormat) params->format); }