use ffmpeg_next as ffmpeg; /* 直接命令行: ffmpeg -re -i /dev/video0 -c:v libx264 -preset veryfast -maxrate 3000K -bufsize 6000K -pix_fmt yuyv422 -g 50 -f rtsp rtsp://127.0.0.1:8554/usba */ fn main() { // #1 初始化 FFmpeg ffmpeg::init().expect("初始化 FFmpeg 失败!"); ffmpeg::log::set_level(ffmpeg::log::Level::Info); // #2 设置输入源 // #2.1 设置输入上下文为 USB摄像头 0 let device_path = "/dev/video0"; let mut ictx = ffmpeg::format::input(&device_path).expect("输入设置 /dev/video0 设备失败"); println!("成功设置输入 /dev/video0 设备!"); // #2.2 获取输入流 let input_stream = ictx.streams() .best(ffmpeg::media::Type::Video) .ok_or(ffmpeg::Error::StreamNotFound).expect("获取输入流失败!"); // 保存下输入流的索引 let input_stream_index = input_stream.index(); // #2.3 解码下输入流 获取流信息 let decoder_ctx = ffmpeg::codec::context::Context::from_parameters(input_stream.parameters()) .expect("解码器创建失败"); let mut decoder = decoder_ctx.decoder().video().expect("输入流解码器信息获取失败!"); let input_width = decoder.width(); let input_height = decoder.height(); let input_pixel_format = decoder.format(); let input_frame_rate = input_stream.avg_frame_rate(); // 打印输入流信息 /* Input #0, video4linux2,v4l2, from '/dev/video0': Duration: N/A, start: 3023781.007502, bitrate: 995328 kb/s Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 1920x1080, 995328 kb/s, 30 fps, 30 tbr, 1000k tbn, 1000k tbc */ ffmpeg::format::context::input::dump(&ictx, 0, Some(&device_path)); // #3 设置编码器 // #3.1 找一下H264硬件编码器,没有就用软件编码器 let codec = ffmpeg::encoder::find_by_name("libx264").expect("查找编码器失败!"); let mut encoder = ffmpeg::codec::context::Context::new_with_codec(codec).encoder().video().expect("编码器获取失败!"); // #3.2 设置编码器参数 encoder.set_width(input_width); encoder.set_height(input_height); encoder.set_format(ffmpeg::format::Pixel::YUV420P); // H.264编码器不支持 yuyv422 encoder.set_time_base(input_frame_rate.invert()); encoder.set_frame_rate(Some(input_frame_rate)); encoder.set_bit_rate(2_000_000); // 2Mbps // #3.3 编码器实时推流优化 let mut encoder_opts = ffmpeg::Dictionary::new(); encoder_opts.set("preset", "ultrafast"); // encoder_opts.set("tune", "zerolatency"); // #3.4 初始化(打开)编码器 let mut opened_encoder = encoder.open_with(encoder_opts).expect("初始化(打开)编码器失败!"); println!("H.264编码器初始化成功: {:?}", opened_encoder.id()); // #4 设置输出源 // #4.1 设置 RTSP 输出上下文 let mut octx = ffmpeg::format::output_as("rtsp://127.0.0.1:8554/usba", "rtsp").expect("设置 RTSP 输出上下文失败!"); // 为 ost 创建一个单独的作用域,以便在其生命周期结束后释放对 octx 的借用 let output_stream_index; let output_time_base; { // 开始 ost 的作用域 // #4.2 添加 编码器到输出上下文,并获取输出流 let mut ost = octx.add_stream(opened_encoder.codec().unwrap()).unwrap(); // 从 opened_encoder 设置参数到输出流 ost // opened_encoder.parameters() 返回 Parameters,set_parameters 接受 Into // &opened_encoder 可以转换为 Parameters ost.set_parameters(&opened_encoder); // 获取流索引和时间基准,后续会用到 output_stream_index = ost.index(); output_time_base = ost.time_base(); } // ost 在这里超出作用域,释放了对 octx 的互斥借用 // #4.3 判断是否需要全局头信息 // 现在 octx 可以安全地被借用 if octx.format().flags().contains(ffmpeg::format::Flags::GLOBAL_HEADER) { opened_encoder.set_flags(ffmpeg::codec::Flags::GLOBAL_HEADER); } // #4.4 写入 RTSP 输出流头部信息 // 现在 octx 可以安全地被互斥借用 octx.write_header().expect("写入 RTSP 输出流头部信息失败!"); // #5 初始化像素格式转换器 let mut scaler = ffmpeg::software::scaling::context::Context::get( input_pixel_format, input_width, input_height, ffmpeg::format::Pixel::YUV420P, input_width, input_height, ffmpeg::software::scaling::Flags::BILINEAR, ).expect("像素格式转换器初始化失败!"); // #6 主循环 let mut frame_count: i64 = 0; let mut decoded_frame = ffmpeg::frame::Video::empty(); let mut scaled_frame = ffmpeg::frame::Video::new(ffmpeg::format::Pixel::YUV420P, input_width, input_height); for (stream, packet) in ictx.packets() { if stream.index() == input_stream_index { // #6.1 解码 decoder.send_packet(&packet).expect("主循环解码失败!"); while decoder.receive_frame(&mut decoded_frame).is_ok() { // #6.2 像素格式转换 scaler.run(&decoded_frame, &mut scaled_frame).expect("主循环像素格式转换失败!"); scaled_frame.set_pts(Some(frame_count)); // #6.3 发送帧到编码器 opened_encoder.send_frame(&scaled_frame).expect("主循环发送帧到编码器失败!"); // #6.4 从编码器获取编码后的帧 let mut encoded_packet = ffmpeg::Packet::empty(); while opened_encoder.receive_packet(&mut encoded_packet).is_ok() { // #6.5 写入 RTSP 输出流 // 使用之前保存的 output_stream_index 和 output_time_base encoded_packet.set_stream(output_stream_index); encoded_packet.rescale_ts(input_frame_rate.invert(), output_time_base); // octx 在这里可以安全地被互斥借用 encoded_packet.write_interleaved(&mut octx).expect("写入 RTSP 输出流失败!"); } frame_count += 1; if frame_count % 100 == 0 { println!("已处理 {} 帧", frame_count); } } } } // #7 清理和结束 println!("正在刷新编码器..."); // #7.1 刷新编码器 opened_encoder.send_eof().unwrap(); let mut encoded_packet = ffmpeg::Packet::empty(); while opened_encoder.receive_packet(&mut encoded_packet).is_ok() { // 使用之前保存的 output_stream_index 和 output_time_base encoded_packet.set_stream(output_stream_index); encoded_packet.rescale_ts(input_frame_rate.invert(), output_time_base); // octx 在这里可以安全地被互斥借用 encoded_packet.write_interleaved(&mut octx).unwrap(); } println!("编码器刷新完毕"); println!("正在写入RTSP尾部信息..."); // octx 在这里可以安全地被互斥借用 octx.write_trailer().unwrap(); println!("RTSP推流结束,共处理 {} 帧", frame_count); }