zoukankan      html  css  js  c++  java
  • 音视频之代码录制音频(六)

    通过代码录音

    权限申请

    在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);
    }
  • 相关阅读:
    P1005 矩阵取数
    [BZOJ2662][BeiJing wc2012]冻结
    [BZOJ1191]超级英雄Hero
    [bzoj1008] 越狱
    [bzoj1001]狼抓兔子 最小割
    网络流24题——负载平衡问题
    分形小山
    可并堆——左偏树、斜堆
    NOIP最优贸易
    洛谷P2073送花
  • 原文地址:https://www.cnblogs.com/muzichenyu/p/15482095.html
Copyright © 2011-2022 走看看