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);
    }
  • 相关阅读:
    POJ 3630 Phone List/POJ 1056 【字典树】
    HDU 1074 Doing Homework【状态压缩DP】
    POJ 1077 Eight【八数码问题】
    状态压缩 POJ 1185 炮兵阵地【状态压缩DP】
    POJ 1806 Manhattan 2025
    POJ 3667 Hotel【经典的线段树】
    状态压缩 POJ 3254 Corn Fields【dp 状态压缩】
    ZOJ 3468 Dice War【PD求概率】
    POJ 2479 Maximum sum【求两个不重叠的连续子串的最大和】
    POJ 3735 Training little cats【矩阵的快速求幂】
  • 原文地址:https://www.cnblogs.com/muzichenyu/p/15482095.html
Copyright © 2011-2022 走看看