CTI is a hobby project I've been working on since 2010. During the 2000s, I had written several of one-off programs involving simple video and audio capture and processing, and network services. Applications included a birdfeeder webcam, recording analog TV programs with an NTSC tuner card, and converting DV tapes to other formats. Every time I needed a new, slightly different application, I'd copy and rename the previous one and change it slightly. I added features to the new programs, and the old ones would bit rot. This bothered me, so I came up with the idea of having one program that could be configured at runtime to handle any of the applications I might come up with, and if I fixed a bug or added a feature, it would be immediately included and available in all of these applications. This was the impetus for CTI.
The last one-off program I wrote was called
ncjpeg, but I forget what the
nc stood for, maybe something to do with
netcat. From my notes,
.2010-Jan-27 16:12:19  Maybe, later, name this "CTI", for "C Templates and Instances". For now, I'm going to build everything under "ncjpeg", and sort it out later. Things I need right now, for getting config values and ranges, are strings, lists of strings, maybe automatic cleanup.
The core concept in CTI is that there are a set of static C structures, with camel-case labels like
SocketServer, that are used as Templates. Any number of them can be Instantiated, wherein a copy of the template is allocated, and a thread is created which runs in a loop calling the
.tick() method of the instance, which usually blocks until it has something to do. Most instances have a set of Input and Output members, which can be connected in a many-to-one Output-to-Input graph, of sorts. Instances may thus pass (loosely runtime-typed) messages to other instances, and that is how a CTI "application" is built. Also, each instance has a table of configuration parameters that can be set using key/value strings.
So, CTI is a modular, multi-threaded, message-passing, runtime-configurable program for video and audio capture and processing, networking, and various other applications.
If you're looking for other projects in the same space, that let you instantiate and plug parts together to do things, here are a few that come to mind,
Why not C++? I spent a few years as a serious aficionado of the language, but I came back to C and have mostly stuck with it. I won't get into the C vs. C++ debate here, but I do acknowledge that several features in CTI could have been implemented more concisely in C++. My philosophy is "each to his own", whatever tools and language a developer is most familiar and comfortable with, are the best.
CTI has a number of compiled-in Templates, each implemented in a separate C file (it can also load
.so modules at runtime). Each Template is registered and added to a set of available templates. To create an Instance and start an associated thread, this function is used,
Instance * Instantiate(const char *label);
label is the name associated with the Template. While CTI could be used as a library, and applications hard-coded to call
Instantiate(), my main design goal of CTI was to allow runtime configurability, so I came up with a simple configuration and command language. Thinking that I would probably come up with something better later on, but not wanting to break previous applications, I implemented it in a file named
ScriptV00.c, allowing for later versions named
ScriptV02, etc. But as is often the case, the original worked good enough for my needs, and I haven't added any other versions.
Ok, now an example.
logitech.cmd is a simple camera viewer application. It assumes a UVC compatible USB camera is available on the computer. Side note, since I hadn't mentioned it thus far, CTI is pretty Linux-centric, although I have occasionally ported it to other platforms, with varying degrees of success.
# Make instances for video capture, Jpeg decompression, and display using SDL. # syntax: new template-label instance-label new V4L2Capture vc new DJpeg dj new SDLstuff sdl # Connect outputs to inputs using runtime-tested labels. # syntax: config source-instance-label message-type-label destination-instance-label connect vc Jpeg_buffer dj connect dj RGB3_buffer sdl # Configure the video capture instance. # syntax: instance-label key value config vc device UVC config vc format MJPG config vc size 640x480 config vc fps 30 # Connect the SDL keyboard to the sdl instance itself to allow quitting with 'q', # and to the video capture instance, which uses 's' for snapshots. # Alternative connect syntax: # connect source-label:message-type-label destination-label:message-type-label connect sdl:Keycode_msg sdl:Keycode_msg connect sdl:Keycode_msg_2 vc:Keycode_msg # Use OVERLAY mode for SDL. Other options are SOFTWARE and GL. config sdl mode OVERLAY # Some extra video capture parameters. config vc Exposure,.Auto.Priority 0 config vc autoexpose 3 # Start the video device capturing, which will set the whole set of instances running. config vc enable 1
The file can be loaded and run with,
CTI (via the ScriptV00 module) will present a
cti> prompt after the file is loaded, from which further
config, and other commands can be entered.
exit will quit the program.
cmd/ subfolder here contains
logitech.cmd and many other experimental files. I've had good experiences with Logitech cameras, specifically models 9000 and C310, so there are several
logitech-*.cmd files. But they should work with most UVC compatible cameras.
External C modules
There are a few files that I've imported into CTI that I did not write.
serf_get.cfor handling HTTP and HTTPS operations. Writing an HTTP client is easy enough, but HTTPS is a big can of worms, so I'm using this example source from Apache Serf, with some small modifications (use
exit(), only initialize once). It works great, and is significantly faster than calling
system("wget ...")on Raspberry Pi (85ms versus 300ms).
encoder_example.cfrom the Xiph Theora library, with some wrapper code around it. I experimented with this a few years ago, but Ogg Theora video never really caught on, and the only decent client support was in Firefox. Side note, if you say "Ogg Theora" to someone, even most tech-savvy people, they'll probably look at you funny.
Some notes about the code
Many of the built-in template modules are incomplete, or just empty skeletons. For example
HTTPClient.c seemed like a good idea one day, but I ended up just calling
wget (and then later importing
There are a few C files that aren't part of CTI, which I wrote for testing, and may or may not have compiled in a long time, but I keep them in the project for possible future reference. I moved some of them into the
Since I use CTI modules in other projects, it has also become convenient place for modules that aren't (yet) built into CTI, but are used in more than one external project.
dbutil.c, and a few others.
String.c handles strings and lists of strings. My favorite function there is
String_sprintf(), which does pretty much what you would expect. There is a special value returned by the function
String_value_none(), which can be used for,
- as a return value from functions that failed to produce a result, and
- for comparison via the function
The advantage over using
NULL is that it points to an existing fixed
String structure, so code that mistakenly accesses an "unset" string or fails to adequately check return values will see
"unset_string_or_empty_result" instead of segfaulting. Once in a while that
"unset_..." string pops up, and it makes it much easier to go back and figure out where I went wrong. I think this is similar to NSULL in Apple's Objective-C libraries.
Since this is C and not C++, there is no
auto_ptr and no garbage collection. I keep my code close to the left margin (minimal levels of conditionals and loops), and I'm not averse to using
goto to jump to the end of the function, where you may find
String_clear() calls for each of the local
String * variables in said function.
In early 2016, I read about a C extension that allows code to define "cleanup" functions for local variables. Its been available in GCC probably for decades, and is also supported in CLANG. For my purposes, I came up with a one-line macro that I put in a header file called
#define localptr(_type, _var) _type * _var __attribute__ ((__cleanup__( _type ## _free )))
and I use it to declare variables like this,
localptr(String, result) = String_value_none();
which expands to,
String * result __attribute__ ((__cleanup__( String_free ))) = String_value_none();
where previously I had done,
String * result = String_value_none(); /* ... */ /* Code, possibly including 'goto out;' */ /* ... */ out: String_free(&result);
String * variables, I simply need to provide this function,
void String_free(String **s)
I love this. I still keep my code simple and close to the left margin, but I no longer have to use goto's and one explicit cleanup call per local variable. I sometimes think about writing a blog post explaing why I choose to use C over C++, I'll write more about this if I get around to it.
I had an experimental project called "modc" in the late 2000s, which implemented garbage collection by means of reference-counting allocations, keeping track of types, and leveraging the descending property of stack variable addresses (on most platforms) to periodically clean up dynamically allocated objects. It worked great, and I even wrote an sshfs-compatible (but non-encrypted) SFTP server completely from scratch with it, but the other goals of the modc project didn't pan out, so I abandoned it. I might try reviving some of the modc concepts in CTI one day.
Using individual modules outside of CTI
Not every program lends itself to the "graph of connected instances" model, and I have several other projects that use various C modules from the CTI project, mostly
SourceSink.c. I've tried to minimize the dependencies between modules so they can be used independently without dragging in the entire CTI project.