防止录音时 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;
}
头文件定义就不贴了
通过 git log 切换到与当前标题对应的 commit 可以看到对应的代码