以下是一个使用 Rust 实现的基础屏幕录制程序示例,支持导出为 MP4,并提供绿色部署的说明。该示例基于跨平台屏幕捕获库和 FFmpeg 编码库,适用于 Windows/macOS/Linux(需根据系统调整依赖)。
一、项目准备
1. 创建项目
cargo new screen_recorder
cd screen_recorder
2. 添加依赖(Cargo.toml
)
[package]
name = "screen_recorder"
version = "0.1.0"
edition = "2021"
[dependencies]
# 屏幕捕获(跨平台)
screen-capture-rs = "0.3.0"
# 视频编码/封装(FFmpeg 绑定)
ffmpeg = { version = "0.5.0", features = ["codec", "format", "software-scaling"] }
# 图像格式转换
image = "0.24.7"
# 线程间通信
crossbeam-channel = "0.5.8"
# 锁优化
parking_lot = "0.12.1"
# 懒加载
lazy_static = "1.4.0"
# 系统事件监听(可选,用于退出)
miette = "5.10.0" # 错误提示
thiserror = "1.0.40" # 自定义错误
二、核心代码实现
以下是简化的录屏逻辑,包含屏幕捕获、RGB 转 YUV420p、H.264 编码、MP4 封装功能。
src/main.rs
use std::sync::Arc;
use std::time::{Duration, Instant};
use crossbeam_channel::{bounded, Receiver, Sender};
use ffmpeg::{
codec, codec::encoder::Video, codec::id::CodeId, format, format::pixel::Pixel, media::Type,
util::frame::video::Video as Frame,
};
use image::{ImageBuffer, Rgb};
use parking_lot::Mutex;
use screen_capture_rs::{ScreenCapturer, ScreenID};
use thiserror::Error;
#[derive(Error, Debug)]
enum Recorder_error {
#[error("FFmpeg error: {0}")]
Ffmpeg(#[from] ffmpeg::Error),
#[error("Channel error")]
Channel,
#[error("No screen available")]
NoScreen,
}
type Result<T> = std::result::Result<T, recorder_error>;
/// 屏幕录制器核心结构体
struct ScreenRecorder {
capturer: ScreenCapturer,
width: u32,
height: u32,
frame_sender: Sender<Arc<Mutex<Frame>>>,
}
impl ScreenRecorder {
/// 初始化屏幕捕获
fn new() -> Result<Self> {
let capturer = ScreenCapturer::new()?;
let screens = capturer.screens()?;
let screen = screens.first().ok_or(recorder_error::NoScreen)?;
let (width, height) = (screen.width, screen.height);
// 创建通道用于传递帧到编码线程
let (frame_sender, frame_receiver) = bounded(10);
Ok(Self {
capturer,
width,
height,
frame_sender,
})
}
/// 开始录制(阻塞直到完成)
fn record(&mut self, output_path: &str, duration: Duration) -> Result<()> {
// 启动编码线程
let encoder = VideoEncoder::new(output_path, self.width, self.height)?;
let receiver = Arc::new(Mutex::new(frame_receiver));
// 启动编码线程
let encoder_thread = std::thread::spawn(move || {
encoder.encode(receiver);
});
// 主线程捕获屏幕帧
let start_time = Instant::now();
while start_time.elapsed() < duration {
// 捕获一帧(阻塞直到有新帧)
let frame = self.capturer.frame()?;
let rgb_frame = ImageBuffer::<Rgb<u8>, Vec<u8>>::from(frame.rgb_pixel_data());
// 转换为 YUV420p 格式(FFmpeg H.264 编码需要)
let yuv_frame = Self::rgb_to_yuv420p(&rgb_frame, self.width, self.height)?;
// 发送到编码线程
if self.frame_sender.send(Arc::new(Mutex::new(yuv_frame))).is_err() {
break;
}
}
// 等待编码线程完成
encoder_thread.join().map_err(|_| recorder_error::Channel)?;
Ok(())
}
/// RGB 转 YUV420p(简化实现,实际需优化)
fn rgb_to_yuv420p(rgb_frame: &ImageBuffer<Rgb<u8>, Vec<u8>>, width: u32, height: u32) -> Result<Frame> {
let mut yuv_frame = Frame::new(width, height, Pixel::Yuv420p);
yuv_frame.planes()[0].copy_from_slice(&rgb_frame.chunks_exact(3).flat_map(|p| [p[0]]).collect::<Vec<u8>>());
// 注意:此处省略 U/V 平面的转换逻辑,实际需完整实现 RGB 到 YUV420p 的转换
Ok(yuv_frame)
}
}
/// H.264 编码器
struct VideoEncoder {
output: format::output::Output,
stream: format::stream::Stream,
codec_ctx: codec::context::Context<Video>,
}
impl VideoEncoder {
/// 初始化编码器
fn new(output_path: &str, width: u32, height: u32) -> Result<Self> {
ffmpeg::init()?;
// 创建输出上下文
let mut output = format::output::Output::create(output_path)?;
output.set_flags(format::flag::Flags::AUTO_TS);
// 查找 H.264 编码器
let codec = codec::encoder::find_by_name("libx264").ok_or(ffmpeg_error::CodecNotFound)?;
let mut codec_ctx = codec::context::Context::new(codec);
codec_ctx.set_width(width);
codec_ctx.set_height(height);
codec_ctx.set_time_base(ffmpeg::Rational::new(1, 30)); // 30 FPS
codec_ctx.set_pix_fmt(Pixel::Yuv420p);
codec_ctx.set_bit_rate(4_000_000); // 4 Mbps
codec_ctx.set_gop_size(10); // 关键帧间隔
// 配置 H.264 参数(可选)
let params = codec_ctx.parameters();
let mut codec_ctx = codec::context::Context::from_parameters(params)?;
codec_ctx.set_option("preset", "ultrafast"); // 快速编码(牺牲压缩率)
codec_ctx.set_option("crf", "23"); // 质量系数(0-51,越小质量越好)
// 打开编码器
codec_ctx.open()?;
// 添加视频流到输出
let stream = output.add_stream(codec)?;
stream.codec().set_parameters(codec_ctx.parameters())?;
// 写入文件头
output.write_header()?;
Ok(Self { output, stream, codec_ctx })
}
/// 编码并写入视频
fn encode(&mut self, receiver: Arc<Mutex<Receiver<Arc<Mutex<Frame>>>>>) {
let mut frame_count = 0;
let start_time = Instant::now();
loop {
// 接收帧(超时 1 秒)
let frame = match receiver.lock().recv_timeout(Duration::from_secs(1)) {
Ok(f) => f.lock(),
Err(_) => break, // 发送端关闭
};
// 设置帧时间戳
let pts = frame_count as i64;
frame.set_pts(pts);
frame_count += 1;
// 发送帧到编码器
self.codec_ctx.send_frame(&frame)?;
// 接收编码后的包
while let Some(packet) = self.codec_ctx.receive_packet()? {
packet.set_stream_index(self.stream.index());
self.output.interleaved_write_packet(&packet)?;
}
}
// 刷新编码器剩余数据
self.codec_ctx.flush()?;
self.output.write_trailer()?;
}
}
fn main() -> Result<()> {
// 录制 10 秒屏幕(可修改)
let mut recorder = ScreenRecorder::new()?;
recorder.record("output.mp4", Duration::from_secs(10))?;
println!("录制完成,输出文件:output.mp4");
Ok(())
}
三、关键说明
1. 屏幕捕获
- 使用
screen-capture-rs
库跨平台捕获屏幕(需系统权限:Windows 需允许屏幕访问,macOS 需开启“屏幕录制”权限,Linux 需 X11 权限)。 ScreenCapturer::frame()
会阻塞直到获取到新帧。
2. 视频编码
- 使用 FFmpeg 的
libx264
编码器(需系统安装 FFmpeg 或静态链接)。 - 注意:示例中
rgb_to_yuv420p
函数未完整实现 RGB 到 YUV420p 的转换(实际需按 BT.601/BT.709 标准转换),建议使用sws_scale
(FFmpeg 的缩放/格式转换函数)优化。
3. MP4 封装
- FFmpeg 自动处理 MP4 容器封装,写入头/尾信息。
四、绿色部署(静态编译)
绿色部署要求生成单个可执行文件,不依赖外部动态库。以 Linux 和 Windows 为例:
1. Linux(静态编译)
# 安装 musl 工具链(Ubuntu/Debian)
sudo apt-get install musl-tools
# 配置 cargo 使用 musl 目标
rustup target add x86_64-unknown-linux-musl
# 编译(静态链接)
cargo build --target x86_64-unknown-linux-musl --release
# 输出文件:target/x86_64-unknown-linux-musl/release/screen_recorder
2. Windows(静态编译)
- 使用 MSVC 工具链(默认)编译的 Windows 可执行文件通常依赖
vcruntime140.dll
,可通过以下方式静态链接:- 在
Cargo.toml
中添加:[target.x86_64-pc-windows-msvc] rustflags = ["-C", "target-feature=+crt-static"]
- 编译:
cargo build --target x86_64-pc-windows-msvc --release
- 输出文件:
target/x86_64-pc-windows-msvc/release/screen_recorder.exe
- 在
五、注意事项
- 性能优化:示例中的 RGB 转 YUV 是简化实现,实际需使用 FFmpeg 的
sws_getCachedContext
进行高效转换。 - 音频支持:如需录制音频,可结合
cpal
库捕获音频,并与视频同步封装(需处理音视频时间戳对齐)。 - 错误处理:示例简化了错误处理,实际需完善边界条件(如屏幕分辨率变化、编码失败等)。
- FFmpeg 依赖:若系统未安装 FFmpeg,需静态链接 FFmpeg 库(如使用 https://github.com/joel16/ffmpeg-musl)。
六、扩展建议
- GUI 界面:添加系统托盘图标或简单 GUI(如使用
egui
或relm4
)。 - 快捷键控制:通过
global-hotkey
库实现全局快捷键(开始/暂停/停止)。 - 实时预览:使用
minifb
或winit
显示录制画面预览。 - 多线程优化:分离屏幕采集、格式转换、编码为独立线程,提升性能。
完整实现需处理更多细节(如时间戳同步、编码参数调优),建议参考 https://ffmpeg.org/doxygen/trunk/ 和 https://github.com/zmwangx/rust-ffmpeg/tree/master/examples。