Skip to content

Commit

Permalink
Add video rendering library
Browse files Browse the repository at this point in the history
  • Loading branch information
mustafaquraish committed Nov 4, 2023
1 parent 158037a commit 39f3f6d
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 1 deletion.
20 changes: 20 additions & 0 deletions std/libc.oc
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,23 @@ def isdigit(c: char): bool extern
def isalpha(c: char): bool extern
def isalnum(c: char): bool extern
def isprint(c: char): bool extern

// unistd.h
@compiler c_include "sys/types.h"
@compiler c_include "sys/wait.h"
@compiler c_include "unistd.h"

def pipe(fds: &i32): i32 extern
def fork(): i32 extern
def dup2(a: i32, b: i32): i32 extern
def close(a: i32): i32 extern
def execvp(a: str, b: &str): i32 extern
def waitpid(a: i32, b: &i32, c: i32): i32 extern
def write(a: i32, b: &u32, c: u32): i32 extern

// macros
const STDIN_FILENO: i32 extern
def WEXITSTATUS(a: i32): i32 extern
def WTERMSIG(a: i32): i32 extern
def WIFEXITED(a: i32): bool extern
def WIFSIGNALED(a: i32): bool extern
139 changes: 139 additions & 0 deletions std/video_renderer/ffmpeg.oc
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
}
46 changes: 46 additions & 0 deletions std/video_renderer/mod.oc
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()
}
}

95 changes: 95 additions & 0 deletions std/video_renderer/sdl.oc
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()
}
2 changes: 1 addition & 1 deletion tests/docs.oc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import std::{ vector, map, compact_map, heap, deque, set }
import std::{ buffer, bufferio, image, json, math, complex, fft, random }
import std::{ value, vec, sdl, glut, socket, sort, thread }
import std::{ value, vec, sdl, glut, socket, sort, thread, video_renderer }
import std::traits::{ hash, eq, compare }

import @parser
Expand Down

0 comments on commit 39f3f6d

Please sign in to comment.