音视频学习笔记 03 使用线程控制录音的停止和开始

38 阅读2分钟

防止录音时 app 界面 freeze, 将 录制的启动放到子线程里面,使得录制动作可以切换开始和停止。

ui 界面的代码

//
//  ViewController.swift
//  myapp
//
//  Created by mac on 2025/6/23.
//

import Cocoa

class ViewController: NSViewController {

    var isRecording = false
    var thread: Thread?
    let btn = NSButton.init(title: "Button", target: nil, action: nil)

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.setFrameSize(NSSize(width: 320, height: 240))

        btn.title = "开始录制"
        btn.frame = NSRect(
            x: 320 / 2 - 60,
            y: 240 / 2 - 15,
            width: 120,
            height: 30
        )
        btn.bezelStyle = .rounded
        btn.setButtonType(.pushOnPushOff)

        // callback
        btn.target = self
        btn.action = #selector(myFunc)

        self.view.addSubview(btn)
    }

    @objc
    func myFunc() {
        self.isRecording = !self.isRecording

        if isRecording {
            thread = Thread.init(
                target: self,
                selector: #selector(self.startRecording),
                object: nil
            )
            thread?.start()
            self.btn.title = "停止录制"
        } else {
            self.btn.title = "开始录制"
            set_record_status(0)
            print("停止录制 pressed")
        }

    }

    @objc
    func startRecording() {
        print("开始录制")
        record_audio()
    }

    override var representedObject: Any? {
        didSet {
            // Update the view, if already loaded.
        }
    }
}

c 代码体

//
//  testc.c
//  myapp
//
//  Created by mac on 2025/6/23.
//

#include "testc.h"

static int record_status = 0;

void set_record_status(int status) { record_status = status; }

void record_audio(void) {
    int ret = 0;
    char errors[1024] = {
        0,
    };

    AVFormatContext *fmt_ctx = NULL;

    // 读出来的数据存储到这个 packet 里面
    AVPacket pkt;
    int count = 0;

    // [[video device]:[audio device]]
    //    char *devicename = ":2";
    char *devicename = ":1";
    //    char *devicename = ":0";

    // 设置日志级别
    av_log_set_level(AV_LOG_DEBUG);

    // 开始录制
    record_status = 1;

    // 1 register audio device
    avdevice_register_all();

    // 2 get format
    // const AVInputFormat *iformat = av_find_input_format("avfoundation");
    const AVInputFormat *input_format = av_find_input_format("avfoundation");

    AVDictionary *options = NULL;
    av_dict_set(&options, "sample_rate", "44100", 0);
    av_dict_set(&options, "channels", "2", 0);

    // 3 open device
    ret = avformat_open_input(&fmt_ctx, devicename, input_format, &options);

    if (ret < 0) {
        av_strerror(ret, errors, 1024);
        printf(stderr, "Failed to open audio device [%d] %s\n", ret, errors);

        return;
    }

    // AVPacket 使用之前要初始化
    //    av_init_packet(&pkt);
    av_new_packet(&pkt, 512);
    //    ret = av_read_frame(fmt_ctx, &pkt);

    //    while (count++ < 500) {
    while (record_status) {
        printf("record_status = %d\n", record_status);
        // read data from device
        while ((ret = av_read_frame(fmt_ctx, &pkt)) == AVERROR(EAGAIN)) {
            // 等待或处理其他任务(如非阻塞模式)
            usleep(1000); // 避免忙等待
        }

        if (ret < 0 && ret != AVERROR_EOF) {
            // 真实错误
            fprintf(stderr, "Error reading packet: %s\n", av_err2str(ret));
        } else if (ret == AVERROR_EOF) {
            // 正常结束
            printf("Reached end of file.\n");
        } else {
            // 成功读取到数据包
            // 处理 pkt...

            av_log(NULL, AV_LOG_INFO, "count = %d, ret = %d, pkt.size = %d\n",
                   count, ret, pkt.size);

            // 每次使用完释放包
            av_packet_unref(&pkt);
        }
    }

    // close device and 释放上下文
    avformat_close_input(&fmt_ctx);

    printf("finish recording \n");

    av_log(NULL, AV_LOG_INFO, "hello world from av_log \n ");

    return;
}

头文件定义就不贴了

代码仓库:gitee.com/dbafu/imooc…

通过 git log 切换到与当前标题对应的 commit 可以看到对应的代码