/** * Device removed callback * * We stop recording here so we don't block the device node */ static void device_removed(const char *dev, void *vptr) { V4L2_DATA(vptr); obs_source_update_properties(data->source); if (strcmp(data->device_id, dev)) return; blog(LOG_INFO, "Device %s disconnected", dev); v4l2_terminate(data); }
static obs_properties_t *v4l2_properties(void *vptr) { V4L2_DATA(vptr); obs_properties_t *props = obs_properties_create(); obs_property_t *device_list = obs_properties_add_list(props, "device_id", obs_module_text("Device"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_t *input_list = obs_properties_add_list(props, "input", obs_module_text("Input"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_t *format_list = obs_properties_add_list(props, "pixelformat", obs_module_text("VideoFormat"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_t *standard_list = obs_properties_add_list(props, "standard", obs_module_text("VideoStandard"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_visible(standard_list, false); obs_property_t *dv_timing_list = obs_properties_add_list(props, "dv_timing", obs_module_text("DVTiming"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_visible(dv_timing_list, false); obs_property_t *resolution_list = obs_properties_add_list(props, "resolution", obs_module_text("Resolution"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_properties_add_list(props, "framerate", obs_module_text("FrameRate"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_properties_add_bool(props, "buffering", obs_module_text("UseBuffering")); obs_data_t *settings = obs_source_get_settings(data->source); v4l2_device_list(device_list, settings); obs_data_release(settings); obs_property_set_modified_callback(device_list, device_selected); obs_property_set_modified_callback(input_list, input_selected); obs_property_set_modified_callback(format_list, format_selected); obs_property_set_modified_callback(resolution_list, resolution_selected); return props; }
/** * Device removed callback * * We stop recording here so we don't block the device node */ static void device_removed(void *vptr, calldata_t *calldata) { V4L2_DATA(vptr); obs_source_update_properties(data->source); const char *dev; calldata_get_string(calldata, "device", &dev); if (strcmp(data->device_id, dev)) return; blog(LOG_INFO, "Device %s disconnected", dev); v4l2_terminate(data); }
static void v4l2_destroy(void *vptr) { V4L2_DATA(vptr); if (!data) return; v4l2_terminate(data); if (data->device_id) bfree(data->device_id); #if HAVE_UDEV v4l2_unref_udev(data->udev); #endif bfree(data); }
/** * Update the settings for the v4l2 source * * Since there are very few settings that can be changed without restarting the * stream we don't bother to even try. Whenever this is called the currently * active stream (if exists) is stopped, the settings are updated and finally * the new stream is started. */ static void v4l2_update(void *vptr, obs_data_t *settings) { V4L2_DATA(vptr); v4l2_terminate(data); if (data->device_id) bfree(data->device_id); data->device_id = bstrdup(obs_data_get_string(settings, "device_id")); data->input = obs_data_get_int(settings, "input"); data->pixfmt = obs_data_get_int(settings, "pixelformat"); data->standard = obs_data_get_int(settings, "standard"); data->dv_timing = obs_data_get_int(settings, "dv_timing"); data->resolution = obs_data_get_int(settings, "resolution"); data->framerate = obs_data_get_int(settings, "framerate"); data->sys_timing = obs_data_get_bool(settings, "system_timing"); v4l2_init(data); }
static void v4l2_destroy(void *vptr) { V4L2_DATA(vptr); if (!data) return; v4l2_terminate(data); if (data->device_id) bfree(data->device_id); #if HAVE_UDEV signal_handler_t *sh = v4l2_get_udev_signalhandler(); signal_handler_disconnect(sh, "device_added", device_added, data); signal_handler_disconnect(sh, "device_removed", device_removed, data); v4l2_unref_udev(); #endif bfree(data); }
/* * Worker thread to get video data */ static void *v4l2_thread(void *vptr) { V4L2_DATA(vptr); int r; fd_set fds; uint8_t *start; uint64_t frames; uint64_t first_ts; struct timeval tv; struct v4l2_buffer buf; struct obs_source_frame out; size_t plane_offsets[MAX_AV_PLANES]; if (v4l2_start_capture(data->dev, &data->buffers) < 0) goto exit; frames = 0; first_ts = 0; v4l2_prep_obs_frame(data, &out, plane_offsets); while (os_event_try(data->event) == EAGAIN) { FD_ZERO(&fds); FD_SET(data->dev, &fds); tv.tv_sec = 1; tv.tv_usec = 0; r = select(data->dev + 1, &fds, NULL, NULL, &tv); if (r < 0) { if (errno == EINTR) continue; blog(LOG_DEBUG, "select failed"); break; } else if (r == 0) { blog(LOG_DEBUG, "select timeout"); continue; } buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (v4l2_ioctl(data->dev, VIDIOC_DQBUF, &buf) < 0) { if (errno == EAGAIN) continue; blog(LOG_DEBUG, "failed to dequeue buffer"); break; } out.timestamp = timeval2ns(buf.timestamp); if (!frames) first_ts = out.timestamp; out.timestamp -= first_ts; start = (uint8_t *) data->buffers.info[buf.index].start; for (uint_fast32_t i = 0; i < MAX_AV_PLANES; ++i) out.data[i] = start + plane_offsets[i]; obs_source_output_video(data->source, &out); if (v4l2_ioctl(data->dev, VIDIOC_QBUF, &buf) < 0) { blog(LOG_DEBUG, "failed to enqueue buffer"); break; } frames++; } blog(LOG_INFO, "Stopped capture after %"PRIu64" frames", frames); exit: v4l2_stop_capture(data->dev); return NULL; }