音视频 Day 07 音频录制 -- 编程

261 阅读1分钟

1. 录音的五大步骤?

  • 注册设备
  • 获取输入格式对象
  • 打开设备
  • 采集数据
  • 释放资源

2. mac 平台需要给录音项目添加权限

  • .pro 文件中加入如下代码配置
QMAKE_INFO_PLIST = Info.plist
  • Info.plist 文件中加入如下权限声明
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSMicrophoneUsageDescription</key>
    <string>record from the microphone</string>
    <key>NSCameraUsageDescription</key>
    <string>record from the camera</string>
</dict>
</plist>

3. 最直白的录音代码

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QFile>

extern "C" {
// 设备相关 API
#include <libavdevice/avdevice.h>
// 格式相关 API
#include <libavformat/avformat.h>
// 工具相关 API(比如错误处理)
#include <libavutil/avutil.h>
}

#define FMT_NAME "avfoundation"
#define DEVICE_NAME ":0"
#define FILE_NAME "/Users/linsipei/Documents/ffmpeg_workspcace/out.pcm"


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_startRecordAudioButton_clicked()
{
    qDebug() << "on_startRecordAudioButton_clicked";

    // ①初始化 libavdevice 并注册所有输入和输出设备
    avdevice_register_all();

    // ②根据格式名称获取输入格式对象,后面需要利用输入格式对象打开设备。
    AVInputFormat *fmt = av_find_input_format(FMT_NAME);
    if(!fmt) {
        //如果找不到输入格式
        qDebug() << "找不到输入格式" << FMT_NAME;
        return;
    }

    // ③根据 avfoundation 打开真正的录音设备
    // 格式上下文(后面通过格式上下文操作设备)
    AVFormatContext *ctx = nullptr;
    // 打开设备
    int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr);
    // 如果打开设备失败
    if (ret < 0) {
        char errbuf[1024] = {0};
        // 根据函数返回的错误码获取错误信息
        av_strerror(ret, errbuf, sizeof (errbuf));
        qDebug() << "打开设备失败" << ret;
        return;
    }

    qDebug() << "打开设备成功" << ret;

    // ④根据上下文对象,进行录音
    // 文件名
    QFile file(FILE_NAME);

    // 打开文件
    // WriteOnly:只读模式。如果文件不存在,就创建文件;如果文件存在,就会清空文件内容
    if (!file.open(QFile::WriteOnly)) {
        qDebug() << "文件打开失败" << FILE_NAME;

        // 关闭设备
        avformat_close_input(&ctx);
        return;
    }

    qDebug() << "打开文件成功";

    // 采集次数(Mac 上容易失败)
    int count = 5000000;
    // 数据包
    AVPacket pkt;

    // 不断采集数据
    while (count-- > 0 ) {
        // 将数据写入文件
        int reslutCode = av_read_frame(ctx,&pkt);
        if ( reslutCode >= 0) {
            file.write((const char*)pkt.data, pkt.size);
            qDebug() << "录音成功:" << reslutCode;

        } else {
            continue;
        }

    }

    // 释放资源:关闭文件
    file.close();

    // 释放资源:关闭设备
    avformat_close_input(&ctx);

    qDebug() << "录音结束" ;
}

4. 使用多线程技术录音


#include "audiothread.h"
#include <QDebug>
#include <QFile>
#include <QDateTime>

extern "C" {
// 设备
#include <libavdevice/avdevice.h>
// 格式
#include <libavformat/avformat.h>
// 工具(比如错误处理)
#include <libavutil/avutil.h>
}


#define FMT_NAME "avfoundation"
#define DEVICE_NAME ":0"
#define FILEPATH "/Users/linsipei/Documents/ffmpeg_workspcace/out.pcm"

AudioThread::AudioThread(QObject *parent) : QThread(parent)
{

}

AudioThread::~AudioThread() {
    qDebug() << "AudioThread 销毁了" ;
}

void AudioThread::setStop(bool stop) {
    _stop = stop;
}

void AudioThread::run() {
    qDebug() << this << "开始执行 run -----";


    // ②根据格式名称获取输入格式对象,后面需要利用输入格式对象打开设备。
    AVInputFormat *fmt = av_find_input_format(FMT_NAME);
    if(!fmt) {
        //如果找不到输入格式
        qDebug() << "找不到输入格式" << FMT_NAME;
        return;
    }

    qDebug() << "②成功" ;

    // ③根据 avfoundation 打开真正的录音设备
    // 格式上下文(后面通过格式上下文操作设备)
    AVFormatContext *ctx = nullptr;
    // 打开设备
    int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr);
    // 如果打开设备失败
    if (ret < 0) {
        char errbuf[1024] = {0};
        // 根据函数返回的错误码获取错误信息
        av_strerror(ret, errbuf, sizeof (errbuf));
        qDebug() << "打开设备失败" << ret;
        return;
    }

    qDebug() << "③成功" ;
    qDebug() << "打开设备成功" << ret;

    // ④根据上下文对象,进行录音
    // 文件名
    QFile file(FILEPATH);

    // 打开文件
    // WriteOnly:只读模式。如果文件不存在,就创建文件;如果文件存在,就会清空文件内容
    if (!file.open(QFile::WriteOnly)) {
        qDebug() << "文件打开失败" << FILEPATH;

        // 关闭设备
        avformat_close_input(&ctx);
        return;
    }

    qDebug() << "④成功" ;
    qDebug() << "打开文件成功";

    // 数据包
    AVPacket pkt;

    // 不断采集数据
    while (!_stop) {
        // 将数据写入文件
        int reslutCode = av_read_frame(ctx,&pkt);
        if ( reslutCode >= 0) {
            file.write((const char*)pkt.data, pkt.size);
            qDebug() << "录音成功:" << reslutCode;

        } else {
            continue;
        }

    }

    // 释放资源:关闭文件
    file.close();

    // 释放资源:关闭设备
    avformat_close_input(&ctx);

    qDebug() << "录音正常结束" ;
}