-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
158037a
commit 39f3f6d
Showing
5 changed files
with
301 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import std::vector::Vector | ||
import std::image::{ Image, Color } | ||
import std::libc:: { | ||
pipe, fork, dup2, close, execvp, waitpid, write, exit, | ||
WEXITSTATUS, WTERMSIG, WIFEXITED, WIFSIGNALED, STDIN_FILENO | ||
} | ||
|
||
const READ_END: u32 = 0 | ||
const WRITE_END: u32 = 1 | ||
|
||
struct FFMPEGContext { | ||
pipe: i32 | ||
pid: i32 | ||
width: u32 | ||
} | ||
|
||
def FFMPEGContext::create(width: u32, height: u32, fps: u32, out_name: str, verbose: bool = false): &FFMPEGContext { | ||
let pipefd: [i32; 2] | ||
|
||
if (pipe(pipefd) < 0) { | ||
println(`FFMPEG: Could not create a pipe`); | ||
return null; | ||
} | ||
|
||
let child = fork(); | ||
if (child < 0) { | ||
println(`FFMPEG: Could not fork`); | ||
return null; | ||
} | ||
|
||
if (child == 0) { | ||
if (dup2(pipefd[READ_END], STDIN_FILENO) < 0) { | ||
println(`FFMPEG CHILD: Could not dup2`); | ||
std::exit(1); | ||
} | ||
close(pipefd[WRITE_END]); | ||
|
||
let args = Vector<str>::new() | ||
args.push("ffmpeg") | ||
if verbose { | ||
args.push("-loglevel"); args.push("verbose") | ||
} | ||
args.push("-y") | ||
args.push("-f"); args.push("rawvideo") | ||
args.push("-pix_fmt"); args.push("rgba") | ||
args.push("-s"); args.push(`{width}x{height}`) | ||
args.push("-r"); args.push(`{fps}`) | ||
args.push("-i"); args.push("-") | ||
args.push("-c:v"); args.push("libx264") | ||
args.push("-vb"); args.push("2500k") | ||
args.push("-c:a"); args.push("aac") | ||
args.push("-ab"); args.push("200k") | ||
args.push("-pix_fmt"); args.push("yuv420p") | ||
args.push(out_name) | ||
args.push(null) | ||
|
||
let ret = execvp("ffmpeg", args.data) | ||
if (ret < 0) { | ||
println(`FFMPEG CHILD: Could not execvp`); | ||
exit(1); | ||
} | ||
assert false, "unreachable" | ||
} | ||
|
||
if (close(pipefd[READ_END]) < 0) { | ||
println(`FFMPEG: Could not close pipefd[READ_END]`); | ||
} | ||
|
||
let ffmpeg = std::new<FFMPEGContext>(); | ||
ffmpeg.pid = child; | ||
ffmpeg.pipe = pipefd[WRITE_END]; | ||
ffmpeg.width = width; | ||
return ffmpeg; | ||
} | ||
|
||
def FFMPEGContext::finish(&this): bool { | ||
let pipe = .pipe; | ||
let pid = .pid; | ||
std::libc::free(this) | ||
|
||
if (close(pipe) < 0) { | ||
println(`FFMPEG: Could not close pipe`); | ||
return false; | ||
} | ||
|
||
while true { | ||
let wstatus = 0i32 | ||
if (waitpid(pid, &wstatus, 0) < 0) { | ||
println(`FFMPEG: waitpid failed`); | ||
return false; | ||
} | ||
|
||
if (WIFEXITED(wstatus)) { | ||
let exit_status = WEXITSTATUS(wstatus); | ||
if (exit_status != 0) { | ||
println(`FFMPEG: ffmpeg exited with status {exit_status}`); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
if (WIFSIGNALED(wstatus)) { | ||
println(`FFMPEG: ffmpeg was killed by signal {WTERMSIG(wstatus)}`); | ||
return false; | ||
} | ||
} | ||
|
||
assert false, "unreachable" | ||
} | ||
|
||
def FFMPEGContext::send_frame(&this, img: &Image): bool { | ||
let data = std::new<u32>(.width * img.height); | ||
let padding = .width - img.width; | ||
for let y = 0; y < img.height; ++y { | ||
let out_x = 0 | ||
for let x = 0; x < padding/2; ++x { | ||
data[y * .width + out_x] = 0; | ||
++out_x; | ||
} | ||
for let x = 0; x < img.width; ++x { | ||
let col = img.get(x, y); | ||
let c = (255 << 24) | (col.b as u32 << 16) | (col.g as u32 << 8) | (col.r as u32); | ||
data[y * .width + out_x] = c; | ||
++out_x; | ||
} | ||
for let x = padding/2; x < padding; ++x { | ||
data[y * .width + out_x] = 0; | ||
++out_x; | ||
} | ||
} | ||
if write(.pipe, data, .width * img.height * 4) < 0 { | ||
println(`FFMPEG: Could not write to pipe`) | ||
std::libc::free(data) | ||
return false | ||
} | ||
std::libc::free(data) | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import std::sdl | ||
|
||
import .ffmpeg::FFMPEGContext | ||
import .sdl::SDLContext | ||
import std::image::{Image, Color} | ||
|
||
|
||
enum VideoRendererType { | ||
FFMPEG | ||
SDL | ||
} | ||
|
||
union VideoRendererUnion { | ||
ffmpeg: &FFMPEGContext | ||
sdl: &SDLContext | ||
} | ||
|
||
struct VideoRenderer { | ||
type: VideoRendererType | ||
u: VideoRendererUnion | ||
} | ||
|
||
def VideoRenderer::create(type: VideoRendererType, width: u32, height: u32, fps: u32, out_name: str = "out.mp4", verbose: bool = false): &VideoRenderer { | ||
let vr = std::new<VideoRenderer>() | ||
vr.type = type | ||
match type { | ||
FFMPEG => vr.u.ffmpeg = FFMPEGContext::create(width, height, fps, out_name, verbose) | ||
SDL => vr.u.sdl = SDLContext::create(width, height, fps) | ||
} | ||
return vr | ||
} | ||
|
||
def VideoRenderer::send_frame(&this, img: &Image) { | ||
match .type { | ||
FFMPEG => .u.ffmpeg.send_frame(img) | ||
SDL => .u.sdl.send_frame(img) | ||
} | ||
} | ||
|
||
def VideoRenderer::finish(&this) { | ||
match .type { | ||
FFMPEG => .u.ffmpeg.finish() | ||
SDL => .u.sdl.finish() | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
|
||
import std::sdl | ||
import std::image::{Image, Color} | ||
|
||
struct SDLContext { | ||
window: &sdl::Window | ||
renderer: &sdl::Renderer | ||
buffer: &sdl::Texture | ||
orig_width: u32 | ||
|
||
frame_time: u32 | ||
last_frame: i32 | ||
} | ||
|
||
def SDLContext::create(width: u32, height: u32, fps: u32): &SDLContext { | ||
let ctx = std::new<SDLContext>() | ||
sdl::init(sdl::INIT_EVERYTHING) | ||
sdl::create_window_renderer(width as i32, height as i32, 0, &ctx.window, &ctx.renderer) | ||
assert ctx.window?, "Failed to create window" | ||
assert ctx.renderer?, "Failed to create renderer" | ||
|
||
ctx.renderer.set_draw_color(0, 0, 0, 255) | ||
ctx.renderer.clear() | ||
ctx.renderer.present() | ||
|
||
ctx.orig_width = width | ||
ctx.buffer = ctx.renderer.create_texture( | ||
sdl::PIXELFORMAT_ABGR8888, | ||
sdl::TEXTUREACCESS_STREAMING, | ||
width as i32, | ||
height as i32 | ||
) | ||
assert ctx.buffer?, "Failed to create buffer" | ||
|
||
ctx.frame_time = 1000 / fps | ||
ctx.last_frame = sdl::get_ticks() | ||
return ctx | ||
} | ||
|
||
def SDLContext::finish(&this) { | ||
.renderer.destroy() | ||
.window.destroy() | ||
sdl::quit() | ||
std::exit(0) | ||
} | ||
|
||
def SDLContext::send_frame(&this, img: &Image) { | ||
let e: sdl::Event | ||
let quit = false | ||
while sdl::poll_event(&e) { | ||
match e.type { | ||
Quit => quit = true | ||
KeyDown => { | ||
match e.key.keysym.scancode { | ||
Q | Escape => quit = true | ||
else => {} | ||
} | ||
} | ||
else => {} | ||
} | ||
} | ||
if quit then .finish() | ||
|
||
let current_tick = sdl::get_ticks() | ||
let elapsed = current_tick - .last_frame | ||
if elapsed < .frame_time as i32 { | ||
sdl::delay(.frame_time as i32 - elapsed) | ||
} | ||
|
||
let data: &u8 | ||
let i_pitch = 0i32 | ||
.buffer.lock(null, (&data) as &untyped_ptr, &i_pitch) | ||
|
||
assert i_pitch > 0 | ||
let pitch = i_pitch as u32 | ||
|
||
let padding = .orig_width - img.width; | ||
for let y = 0; y < img.height; ++y { | ||
let out_x = padding/2 | ||
for let x = 0; x < img.width; ++x { | ||
let col = img.get(x, y); | ||
let off = y * pitch + out_x * 4 | ||
data[off + 0] = col.r | ||
data[off + 1] = col.g | ||
data[off + 2] = col.b | ||
data[off + 3] = 255 as u8 | ||
++out_x; | ||
} | ||
} | ||
|
||
.buffer.unlock() | ||
.renderer.copy(.buffer, null, null) | ||
.renderer.present() | ||
.last_frame = sdl::get_ticks() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters