[vlc-devel] [PATCH 1/2] libvlc: Add Rust API for writing modules in rust

Kartik Ohri kartikohri13 at gmail.com
Sat Sep 19 23:09:33 CEST 2020


The API is written in two layers. vlccore-sys
crate contains one to one bindings of libvlc
functions. vlccore-rs crate wraps vlccore-sys and
exposes a Rust idiomatic API for writing modules
in rust. As of now, the API exposes only a subset
of the vlc_stream API. More functionality can be
added to Rust API as required in the future.
---
 .gitignore                    |   2 +-
 configure.ac                  |  13 ++++
 modules/common.am             |   7 ++
 src/Cargo.toml                |   2 +
 src/vlccore-rs/Cargo.toml     |  10 +++
 src/vlccore-rs/src/lib.rs     |   2 +
 src/vlccore-rs/src/stream.rs  | 126 ++++++++++++++++++++++++++++++++++
 src/vlccore-rs/src/utils.rs   |  19 +++++
 src/vlccore-sys/Cargo.toml    |  10 +++
 src/vlccore-sys/src/lib.rs    |   3 +
 src/vlccore-sys/src/stream.rs |  46 +++++++++++++
 11 files changed, 239 insertions(+), 1 deletion(-)
 create mode 100644 src/Cargo.toml
 create mode 100644 src/vlccore-rs/Cargo.toml
 create mode 100644 src/vlccore-rs/src/lib.rs
 create mode 100644 src/vlccore-rs/src/stream.rs
 create mode 100644 src/vlccore-rs/src/utils.rs
 create mode 100644 src/vlccore-sys/Cargo.toml
 create mode 100644 src/vlccore-sys/src/lib.rs
 create mode 100644 src/vlccore-sys/src/stream.rs

diff --git a/.gitignore b/.gitignore
index fc368212c8..6c049a80b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,7 +46,7 @@ wxvlc
 vlc_install_dir/*
 plugins.dat
 patches/*
-
+**/Cargo.lock
 include/vlc/libvlc_version.h
 
 # Ignore build dirs
diff --git a/configure.ac b/configure.ac
index 99e4669942..53d56a725c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1896,6 +1896,19 @@ AS_IF([test "${enable_sout}" != "no"], [
 ])
 AM_CONDITIONAL([ENABLE_SOUT], [test "${enable_sout}" != "no"])
 
+dnl Rust Modules
+AC_ARG_ENABLE([rust],
+    AS_HELP_STRING([--enable-rust], [enable building Rust modules (default disabled)]))
+AS_IF([test "${enable_rust}" = "yes"],
+      [AC_DEFINE(ENABLE_RUST, 1, [Define to 1 for building rust modules.])])
+AM_CONDITIONAL([BUILD_RUST], [test "${enable_rust}" = "yes"])
+if test "${enable_rust}" = "yes"
+then
+    AC_CHECK_PROG(CARGO, [cargo], [yes], [no])
+    AS_IF([test "x$CARGO" = "xno"],
+          AC_MSG_ERROR([cargo not found. cargo is required to build rust modules]))
+fi
+
 dnl Lua modules
 AC_ARG_ENABLE([lua],
   AS_HELP_STRING([--disable-lua],
diff --git a/modules/common.am b/modules/common.am
index b991f6ce2f..5edc14ea26 100644
--- a/modules/common.am
+++ b/modules/common.am
@@ -39,6 +39,13 @@ endif
 AM_YFLAGS = -d -Wno-yacc
 
 SUFFIXES = .l .y .asm
+RUST_API = ../src/vlccore-sys/src/lib.rs \
+	../src/vlccore-sys/src/stream.rs \
+	../src/vlccore-sys/Cargo.toml \
+	../src/vlccore-rs/src/lib.rs \
+	../src/vlccore-rs/src/stream.rs \
+	../src/vlccore-rs/Cargo.toml \
+	../src/Cargo.toml
 
 .asm.lo:
 	$(LIBTOOL) --mode=compile --tag=ASM $(X86ASM) $(X86ASMFLAGS) $(X86ASMDEFS) -I$(top_srcdir)/extras/include/x86/ $< -o $@
diff --git a/src/Cargo.toml b/src/Cargo.toml
new file mode 100644
index 0000000000..4b6346fc23
--- /dev/null
+++ b/src/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["vlccore-sys", "vlccore-rs"]
\ No newline at end of file
diff --git a/src/vlccore-rs/Cargo.toml b/src/vlccore-rs/Cargo.toml
new file mode 100644
index 0000000000..108c849f80
--- /dev/null
+++ b/src/vlccore-rs/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "vlccore-rs"
+version = "0.1.0"
+authors = ["Kartik Ohri <kartikohri13 at gmail.com>"]
+edition = "2018"
+license = "LGPL-2.1-or-later"
+
+[dependencies]
+libc = "0.2"
+vlccore-sys = {path="../vlccore-sys"}
\ No newline at end of file
diff --git a/src/vlccore-rs/src/lib.rs b/src/vlccore-rs/src/lib.rs
new file mode 100644
index 0000000000..9fffcbc8b5
--- /dev/null
+++ b/src/vlccore-rs/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod stream;
+pub mod utils;
diff --git a/src/vlccore-rs/src/stream.rs b/src/vlccore-rs/src/stream.rs
new file mode 100644
index 0000000000..afa622b4f1
--- /dev/null
+++ b/src/vlccore-rs/src/stream.rs
@@ -0,0 +1,126 @@
+use libc::{c_char, c_void};
+use std::ffi::{CStr, CString};
+use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom};
+use std::ptr::null_mut;
+use vlccore_sys::stream as ffi;
+
+pub struct Stream {
+    stream: *mut ffi::stream_t,
+}
+
+impl Read for Stream {
+    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
+        let bytes_read: isize = unsafe {
+            ffi::vlc_stream_Read(self.stream, buf.as_mut_ptr() as *mut c_void, buf.len())
+        };
+        if bytes_read < 0 {
+            Err(Error::new(
+                ErrorKind::Other,
+                "Error while reading from stream",
+            ))
+        } else {
+            Ok(bytes_read as usize)
+        }
+    }
+}
+
+impl Seek for Stream {
+    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
+        let offset = match pos {
+            SeekFrom::Start(n) => n,
+            SeekFrom::Current(n) => {
+                let len = self.position();
+                if n >= 0 {
+                    len.checked_add(n as u64)
+                } else {
+                    len.checked_sub(n.wrapping_neg() as u64)
+                }
+                .ok_or(Error::new(ErrorKind::Other, "Position Overflow"))?
+            }
+            SeekFrom::End(n) => self.len().and_then(|position| {
+                if n >= 0 {
+                    position.checked_add(n as u64)
+                } else {
+                    position.checked_sub(n.wrapping_neg() as u64)
+                }
+                .ok_or(Error::new(ErrorKind::Other, "Position Overflow"))
+            })?,
+        };
+
+        let result = unsafe { ffi::vlc_stream_Seek(self.stream, offset) };
+        if result != 0 {
+            return Err(Error::new(ErrorKind::Other, "Could not seek stream"));
+        }
+        Ok(offset)
+    }
+}
+
+impl Drop for Stream {
+    fn drop(&mut self) {
+        unsafe { ffi::vlc_stream_Delete(self.stream) }
+    }
+}
+
+impl From<*mut ffi::stream_t> for Stream {
+    fn from(stream: *mut ffi::stream_t) -> Self {
+        Stream { stream }
+    }
+}
+
+impl Stream {
+    pub fn len(&self) -> std::result::Result<u64, Error> {
+        let size: u64 = 0;
+        let result = unsafe {
+            ffi::vlc_stream_Control(
+                self.stream,
+                ffi::stream_query_e_STREAM_GET_SIZE as i32,
+                &size,
+            )
+        };
+        if result != 0 {
+            return Err(Error::new(
+                ErrorKind::Other,
+                "Could not determine stream size",
+            ));
+        }
+        Ok(size)
+    }
+
+    pub fn position(&self) -> u64 {
+        unsafe { ffi::vlc_stream_Tell(self.stream) }
+    }
+
+    pub fn eof(&self) -> Result<bool> {
+        Ok(unsafe { ffi::vlc_stream_Eof(self.stream) })
+    }
+
+    pub fn mimetype(&self) -> Option<String> {
+        let content_type = self.contenttype()?;
+        let index = content_type.chars().position(|c| c == ';')?;
+        Some(content_type[..index].to_owned())
+    }
+
+    pub fn contenttype(&self) -> Option<String> {
+        let result: *mut c_char = null_mut();
+        unsafe {
+            if ffi::vlc_stream_Control(
+                self.stream,
+                ffi::stream_query_e_STREAM_GET_CONTENT_TYPE as i32,
+                result,
+            ) != 0
+            {
+                return None;
+            } else if result.is_null() {
+                return None;
+            }
+            // FIXME: Free the C pointer received but retain copy of data in Rust
+            CStr::from_ptr(result).to_str().map(|s| s.to_string()).ok()
+        }
+    }
+
+    pub fn new_with_url(&mut self, url: &str) -> Self {
+        let c_url = CString::new(url).unwrap().as_ptr();
+        let stream = unsafe { ffi::vlc_stream_NewURL(self.stream, c_url) };
+        Stream { stream }
+    }
+}
diff --git a/src/vlccore-rs/src/utils.rs b/src/vlccore-rs/src/utils.rs
new file mode 100644
index 0000000000..3d9e25ac31
--- /dev/null
+++ b/src/vlccore-rs/src/utils.rs
@@ -0,0 +1,19 @@
+use libc::{c_char, malloc};
+use std::ffi::CString;
+use std::ptr::null_mut;
+use std::slice::from_raw_parts_mut;
+
+pub unsafe fn convert_string_to_ptr(string: String) -> *mut c_char {
+    if string.is_empty() {
+        return std::ptr::null_mut();
+    }
+    let size = string.len() + 1;
+    CString::new(string)
+        .map(|ptr| {
+            let c_ptr = malloc(size * std::mem::size_of::<u8>()) as *mut u8;
+            let slice = from_raw_parts_mut(c_ptr, size);
+            slice[..size].copy_from_slice(ptr.as_bytes_with_nul());
+            c_ptr as *mut c_char
+        })
+        .unwrap_or(null_mut())
+}
diff --git a/src/vlccore-sys/Cargo.toml b/src/vlccore-sys/Cargo.toml
new file mode 100644
index 0000000000..0dde7b2e7d
--- /dev/null
+++ b/src/vlccore-sys/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "vlccore-sys"
+version = "0.1.0"
+authors = ["Kartik Ohri <kartikohri13 at gmail.com>"]
+edition = "2018"
+license = "LGPL-2.1-or-later"
+
+[dependencies]
+libc = "0.2"
+
diff --git a/src/vlccore-sys/src/lib.rs b/src/vlccore-sys/src/lib.rs
new file mode 100644
index 0000000000..08201d88ba
--- /dev/null
+++ b/src/vlccore-sys/src/lib.rs
@@ -0,0 +1,3 @@
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+pub mod stream;
diff --git a/src/vlccore-sys/src/stream.rs b/src/vlccore-sys/src/stream.rs
new file mode 100644
index 0000000000..e9df363998
--- /dev/null
+++ b/src/vlccore-sys/src/stream.rs
@@ -0,0 +1,46 @@
+use libc::{c_char, c_int, c_void, size_t, ssize_t};
+
+#[repr(C)]
+pub struct stream_t {
+    _private: [u8; 0],
+}
+
+pub const stream_query_e_STREAM_CAN_SEEK: stream_query_e = 0;
+pub const stream_query_e_STREAM_CAN_FASTSEEK: stream_query_e = 1;
+pub const stream_query_e_STREAM_CAN_PAUSE: stream_query_e = 2;
+pub const stream_query_e_STREAM_CAN_CONTROL_PACE: stream_query_e = 3;
+pub const stream_query_e_STREAM_GET_SIZE: stream_query_e = 6;
+pub const stream_query_e_STREAM_GET_PTS_DELAY: stream_query_e = 257;
+pub const stream_query_e_STREAM_GET_TITLE_INFO: stream_query_e = 258;
+pub const stream_query_e_STREAM_GET_TITLE: stream_query_e = 259;
+pub const stream_query_e_STREAM_GET_SEEKPOINT: stream_query_e = 260;
+pub const stream_query_e_STREAM_GET_META: stream_query_e = 261;
+pub const stream_query_e_STREAM_GET_CONTENT_TYPE: stream_query_e = 262;
+pub const stream_query_e_STREAM_GET_SIGNAL: stream_query_e = 263;
+pub const stream_query_e_STREAM_GET_TAGS: stream_query_e = 264;
+pub const stream_query_e_STREAM_SET_PAUSE_STATE: stream_query_e = 512;
+pub const stream_query_e_STREAM_SET_TITLE: stream_query_e = 513;
+pub const stream_query_e_STREAM_SET_SEEKPOINT: stream_query_e = 514;
+pub const stream_query_e_STREAM_SET_RECORD_STATE: stream_query_e = 515;
+pub const stream_query_e_STREAM_SET_PRIVATE_ID_STATE: stream_query_e = 4096;
+pub const stream_query_e_STREAM_SET_PRIVATE_ID_CA: stream_query_e = 4097;
+pub const stream_query_e_STREAM_GET_PRIVATE_ID_STATE: stream_query_e = 4098;
+pub type stream_query_e = ::std::os::raw::c_uint;
+
+pub type vlc_object_t = stream_t;
+
+extern "C" {
+    pub fn vlc_stream_Read(s: *mut stream_t, buf: *mut c_void, len: size_t) -> ssize_t;
+
+    pub fn vlc_stream_Tell(s: *const stream_t) -> u64;
+
+    pub fn vlc_stream_Eof(s: *const stream_t) -> bool;
+
+    pub fn vlc_stream_Seek(s: *mut stream_t, offset: u64) -> c_int;
+
+    pub fn vlc_stream_Control(s: *mut stream_t, query: c_int, ...) -> c_int;
+
+    pub fn vlc_stream_Delete(s: *mut stream_t);
+
+    pub fn vlc_stream_NewURL(obj: *mut vlc_object_t, url: *const c_char) -> *mut stream_t;
+}
-- 
2.25.1



More information about the vlc-devel mailing list