音视频之代码录制音频(六)

通过代码录音

权限申请

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

 

posted @ 2021-10-29 20:23  木子沉雨  阅读(166)  评论(0编辑  收藏  举报