| name | error-handling |
| description | Error checking patterns for both C and Python plus logging. Use when user asks about error handling, debugging ovstream failures, registering a log callback, or troubleshooting. |
Error Handling
Overview
In Python, ovstream raises ovstream.OvstreamError with descriptive messages on failure. The exception carries the C-side status code on .status (an ovstream.ApiStatus enum) so callers can pattern-match on expected failures.
In C, every API call returns ovstream_result_t. Inspect the .status field for OVSTREAM_API_SUCCESS (or use the OVSTREAM_OK(r) convenience macro), and on failure call ovstream_get_last_error() (thread-local) for the human-readable detail string. Status codes: OVSTREAM_API_SUCCESS, _ERROR, _TIMEOUT, _INVALID_ARGUMENT, _INVALID_STATE, _NOT_SUPPORTED.
A separate logging mechanism — the log_callback callback registered at initialize time — captures non-fatal warnings, info, and verbose chatter from the SDK and its dependencies (StreamSDK, GStreamer).
Python
try:
ovstream.initialize(log_min_severity=ovstream.LogLevel.WARNING)
with ovstream.Server(ovstream.ServerType.WEBRTC) as server:
server.start(ovstream.ServerConfig(width=1920, height=1080))
except ovstream.OvstreamError as e:
if e.status == ovstream.ApiStatus.NOT_SUPPORTED:
pass
else:
print(f"ovstream error [{e.status.name}]: {e}", file=sys.stderr)
Source: examples/python/basic_stream/main.py snippet initialize-sdk
stream_video can raise on a per-frame basis (e.g. invalid frame, server not started). Most of the time this is benign (just no client connected yet, and backends usually accept early frames silently). Wrap the call in try/except OvstreamError: pass if you're streaming a polling/idle pattern and want to swallow transient setup-time failures.
C
Source: examples/c/basic_stream/main.cu snippet initialize-sdk
The pattern in basic_stream uses ovstream_result_t returns + OVSTREAM_OK(r) + ovstream_get_last_error():
if (!OVSTREAM_OK(ovstream_start(server, &cfg)))
{
fprintf(stderr, "ovstream_start failed: %s\n", ovstream_get_last_error().ptr);
ovstream_destroy_server(server);
ovstream_shutdown();
return 1;
}
If you need to branch on the specific status:
ovstream_result_t r = ovstream_stream_video(server, &frame);
switch (r.status)
{
case OVSTREAM_API_SUCCESS: break;
case OVSTREAM_API_INVALID_STATE: break;
default:
fprintf(stderr, "stream_video failed: %s\n", ovstream_get_last_error().ptr);
return 1;
}
ovstream_get_last_error() returns an ovstream_string_t view into thread-local storage. The pointer is valid until the next ovstream call on this thread (which may overwrite it). Copy the bytes if you need to retain the string.
A helper template like this is convenient for repeated check-and-print:
static bool check(ovstream_result_t r, std::string_view operation) {
if (!OVSTREAM_OK(r)) {
auto err = ovstream_get_last_error();
std::cerr << "ovstream " << operation << " failed: "
<< std::string_view(err.ptr, err.length) << "\n";
return true;
}
return false;
}
Lifecycle vs hot-loop
The examples follow a deliberate two-tier pattern:
- Lifecycle calls (
ovstream_initialize, ovstream_create_server, ovstream_start, ovstream_stop, ovstream_destroy_server) check the result and exit-or-recover on failure. A failure here is structural and needs attention.
- Hot-loop calls (
ovstream_stream_video, ovstream_stream_audio, per-frame ovstream_send_message) discard the result with (void). Per-frame failures are usually benign (no client connected yet, backend transient during disconnect) and surfacing them per-frame floods logs and obscures the real lifecycle errors that matter. The log_callback (next section) carries the underlying detail when you need it.
In examples/c/basic_stream/main.cu you'll see this exactly: lifecycle calls go through OVSTREAM_OK checks; the per-frame call reads (void)ovstream_stream_video(...). For per-frame diagnostics during development, switch to the switch(r.status) form above and set log_min_severity = OVSTREAM_LOG_VERBOSE to surface the underlying status without crashing the loop.
Log callback
Register a log callback at initialize time to receive non-error messages:
Source: examples/c/basic_stream/main.cu snippet log-callback
The callback fires for SDK-internal messages plus anything GStreamer / StreamSDK log. Use log_min_severity to filter — OVSTREAM_LOG_VERBOSE is firehose-level, OVSTREAM_LOG_WARNING is the recommended default for production, and OVSTREAM_LOG_DEFAULT (= 0, the value a {0}-initialized config produces) is also remapped to WARNING by the SDK so zero-init is quiet by default. OVSTREAM_LOG_NONE suppresses every line including errors.
In Python, pass log_fn= to ovstream.initialize:
ovstream.initialize(
log_fn=lambda level, channel, message, timestamp: print(f"[{level.name}][{channel}] {message}"),
log_min_severity=ovstream.LogLevel.WARNING,
)
The Python log_fn is invoked from internal SDK threads — same threading caveat as the connect/message callbacks.
Log callback semantics
The log callback is only installed on the very first initialize of an init/shutdown lifecycle (the 0→1 ref-count transition). Subsequent initialize calls while the ref-count is non-zero ignore their log_fn argument because the C side does not support replacing the callback while the library is initialized.
To change logging configuration, take the ref-count to zero with matching shutdown calls, then re-initialize.
Key Types / Functions
| Python | C |
|---|
ovstream.OvstreamError (.status is an ApiStatus) | ovstream_result_t + OVSTREAM_OK(r) + ovstream_get_last_error() |
ovstream.ApiStatus.{SUCCESS, ERROR, TIMEOUT, INVALID_ARGUMENT, INVALID_STATE, NOT_SUPPORTED} | OVSTREAM_API_{SUCCESS, ERROR, TIMEOUT, INVALID_ARGUMENT, INVALID_STATE, NOT_SUPPORTED} |
ovstream.LogLevel.{DEFAULT, VERBOSE, INFO, WARNING, ERROR, NONE} | OVSTREAM_LOG_{DEFAULT, VERBOSE, INFO, WARNING, ERROR, NONE} |
ovstream.initialize(log_fn=..., log_min_severity=...) | ovstream_init_config_t { .log_callback = ..., .log_min_severity = ... } |
Common Pitfalls
ovstream_get_last_error is per-thread. Don't read it from a thread other than the one that made the failing call.
- Don't free the error string. It's owned by the SDK's thread-local storage.
- A non-SUCCESS return from
stream_video is often benign — frames pushed before a client connects are typically accepted silently; if a failure does surface, log at verbose and continue. Save ERROR-level handling for initialize, create_server, start.
- Python destructor errors are swallowed.
Server.__del__ catches and discards any exception from the implicit close() so GC never raises an unraisable-exception warning and the init/shutdown ref-count stays paired. The trade-off is that you won't see a destroy-time failure if you skip explicit cleanup. Always prefer explicit server.close() or a with block when you need to surface teardown errors.
- StreamSDK is conservative about ERROR level. A lot of what StreamSDK logs as ERROR is recoverable; ovstream remaps these to WARN to reduce noise. If you see a torrent of warnings during a normal shutdown, that's expected.