如何用 Rust 实现的基础屏幕录制程序?
以下是一个使用 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 FPScodec_ctx.set_pix_fmt(Pixel::Yuv420p);codec_ctx.set_bit_rate(4_000_000); // 4 Mbpscodec_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。