我正在开发一个使用 ascii 字符在控制台中播放视频的程序。 这是此类程序的示例text 为了计算正确的字符,我使用 opencl 进行并行化。 这是代码:
MediaType::Video(video) => {
let fps = video.get(opencv::videoio::CAP_PROP_FPS).unwrap_or(30.0);
let ms_per_frame = (1000.0f64 / fps).floor() as u64;
let mut frame_index = 0;
let chars = crate::ascii::CHARS1;
let mut size = 0;
let mut chars_buffer = unsafe { Buffer::<cl_uchar>::create(&self.context, CL_MEM_READ_ONLY, chars.len(), ptr::null_mut()).unwrap() };
let mut output_buffer = unsafe { Buffer::<cl_uchar>::create(&self.context, CL_MEM_WRITE_ONLY, 1, ptr::null_mut()).unwrap() };
let mut frame_buffer = unsafe { Buffer::<cl_uchar>::create(&self.context, CL_MEM_READ_ONLY, 1, ptr::null_mut()).unwrap() };
let chars_buffer_write_event = unsafe { self.queue.enqueue_write_buffer(&mut chars_buffer, CL_NON_BLOCKING, 0, chars.as_bytes(), &[]) };
let char_len: cl_uint = 1;
let grayscale: cl_uint = 1;
let step: cl_uint = (255.0 / (chars.chars().count() as f32)).ceil() as u32;
let now = SystemTime::now();
loop {
let mut frame = Mat::default();
let result = video.read(&mut frame);
if result.is_err() || !result.unwrap() || frame.empty() {
break;
}
let terminal_size = termion::terminal_size().unwrap();
let new_size = Size::new(terminal_size.0 as i32, terminal_size.1 as i32);
let mut resized_frame = Mat::default();
let result = imgproc::resize(&frame, &mut resized_frame, new_size, 0.0, 0.0, imgproc::INTER_LINEAR);
if result.is_err() || resized_frame.empty() {
break;
}
if grayscale == 1 {
let mut gray_frame = Mat::default();
let result = imgproc::cvt_color(&resized_frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0);
if result.is_err() || gray_frame.empty() {
break;
}
resized_frame = gray_frame;
}
let frame_bytes = resized_frame.data_bytes().unwrap();
if size != frame_bytes.len() {
size = frame_bytes.len();
frame_buffer = unsafe { Buffer::<cl_uchar>::create(&self.context, CL_MEM_READ_ONLY, size, ptr::null_mut()).unwrap() };
output_buffer = unsafe { Buffer::<cl_uchar>::create(&self.context, CL_MEM_WRITE_ONLY, size, ptr::null_mut()).unwrap() };
}
let _ = unsafe { self.queue.enqueue_write_buffer(&mut frame_buffer, CL_BLOCKING, 0, resized_frame.data_bytes().unwrap(), &[]) };
let execute = unsafe {
ExecuteKernel::new(&self.kernel)
.set_arg(&frame_buffer)
.set_arg(&chars_buffer)
.set_arg(&char_len)
.set_arg(&grayscale)
.set_arg(&step)
.set_arg(&output_buffer)
.set_event_wait_list(&[chars_buffer_write_event.as_ref().unwrap().get()])
.set_global_work_size(size)
.enqueue_nd_range(&self.queue).unwrap()
};
let mut string: Vec<cl_uchar> = vec![0; size];
let _ = unsafe { self.queue.enqueue_read_buffer(&output_buffer, CL_BLOCKING, 0, &mut string, &[execute.get()]).unwrap() };
let mut rgb = Vec::new();
if grayscale == 0 {
rgb = frame_bytes.to_vec();
}
self.media_sender.send(StringInfo {string, rgb}).unwrap();
let time = now.elapsed();
if time.is_err() {
break;
}
let time = time.unwrap();
frame_index += 1;
let deadtime_to_frame_preparing = Duration::from_millis(ms_per_frame * frame_index);
if time < deadtime_to_frame_preparing {
sleep(deadtime_to_frame_preparing - time);
continue;
}
let frames_to_skip = (time - deadtime_to_frame_preparing).div_duration_f64(Duration::from_millis(ms_per_frame)).ceil() as u64;
frame_index += frames_to_skip;
{
let mut skipped = Mat::default();
for _ in 0..frames_to_skip {
if !video.read(&mut skipped).unwrap_or(false) || skipped.empty() {
break;
}
}
}
}
__inline void write(__global uchar* output, __global uchar* input, uint output_start_index, uint input_start_index, uint len) {
for (uint i = 0; i < len; i++) {
output[output_start_index + i] = input[input_start_index + i];
}
}
__kernel void calculate(__global uchar* frame, __global uchar* chars, uint char_len, uint grayscale, uint step, __global uchar* out) {
int index = get_global_id(0);
int brightness = frame[index];
if (!grayscale) {
brightness = (frame[index * 3] + frame[index * 3 + 1] + frame[index * 3 + 2]) / 3;
}
int char_index = brightness / step - 1;
write(out, chars, index + char_len, char_index + char_len, char_len);
代码有点不完整,但它可以工作。 一切正常,但播放 10-15 秒后,程序冻结,CPU 使用率增加到 100%。最终可能会导致整个系统冻结。 我不明白这可能是由于什么原因造成的,因为当我使用CPU进行计算时,没有出现这样的问题。也许我错误地使用了 opencl。
如何解决冻结问题?
还有一个问题,我的程序在运行期间使用相对较少的资源 - 处理器的 15-20%,但控制台(我有小猫)使用 60-70%,我可以优化它吗?也许减少每秒帧数或其他
我的处理器是 Ryzen 5 5600 GPU是GeForce 1060
您对 OpenCL 内核进行排队,但从未完成/清空队列。队列爆炸,填满整个 RAM,然后系统冻结。因此,在每次迭代中添加 clFinish。
但是在这里使用 OpenCL 完全是多余的。瓶颈在于将字符转储到控制台的速度。至于选择哪些角色:初始化时创建一个查找表,这样就不需要每帧都计算角色选择了。
这里的关键是最大限度地减少打印字符的数量:对于灰度来说,这是微不足道的(只有一个宽*高字符的缓冲区)。对于颜色,您需要 ASCII 转义序列来更改颜色,并且您可以组合相同颜色的字符块,以使其之间只有一种颜色变化。通过抖动,您可以获得超过 16 种标准颜色。 为了进一步优化,您可以将最后一帧(作为 ASCII)保留在内存中,将新帧与最后一帧进行比较,然后仅重新打印更改的字符。
通过所有这些优化,全彩视频是可行的,请参阅此处。