static void *send_thread(void *data) { struct rtmp_stream *stream = data; bool disconnected = false; while (os_sem_wait(stream->send_sem) == 0) { struct encoder_packet packet; if (os_event_try(stream->stop_event) != EAGAIN) break; if (!get_next_packet(stream, &packet)) continue; if (send_packet(stream, &packet, false) < 0) { disconnected = true; break; } } if (!disconnected && !send_remaining_packets(stream)) disconnected = true; if (disconnected) { blog(LOG_INFO, "Disconnected from %s", stream->path.array); free_packets(stream); } if (os_event_try(stream->stop_event) == EAGAIN) { pthread_detach(stream->send_thread); obs_output_signal_stop(stream->output, OBS_OUTPUT_DISCONNECTED); } stream->active = false; return NULL; }
static void *sinewave_thread(void *pdata) { struct sinewave_data *swd = pdata; uint64_t last_time = os_gettime_ns(); uint64_t ts = 0; double cos_val = 0.0; uint8_t bytes[480]; while (os_event_try(swd->event) == EAGAIN) { if (!os_sleepto_ns(last_time += 10000000)) last_time = os_gettime_ns(); for (size_t i = 0; i < 480; i++) { cos_val += rate * M_PI_X2; if (cos_val > M_PI_X2) cos_val -= M_PI_X2; double wave = cos(cos_val) * 0.5; bytes[i] = (uint8_t)((wave+1.0)*0.5 * 255.0); } struct obs_source_audio data; data.data[0] = bytes; data.frames = 480; data.speakers = SPEAKERS_MONO; data.samples_per_sec = 48000; data.timestamp = ts; data.format = AUDIO_FORMAT_U8BIT; obs_source_output_audio(swd->source, &data); ts += 10000000; } return NULL; }
/* * Loop to skip the first few samples of a stream */ static int pulse_skip(struct pulse_data *data) { uint64_t skip = 1; const void *frames; size_t bytes; uint64_t pa_time; while (os_event_try(data->event) == EAGAIN) { pulse_iterate(data); pa_stream_peek(data->stream, &frames, &bytes); if (!bytes) continue; if (!frames || pa_stream_get_time(data->stream, &pa_time) < 0) { pa_stream_drop(data->stream); continue; } if (skip == 1 && pa_time) skip = pa_time; if (skip + pulse_start_delay < pa_time) return 0; pa_stream_drop(data->stream); } return -1; }
static void *audio_thread(void *param) { struct audio_output *audio = param; uint64_t buffer_time = audio->info.buffer_ms * 1000000; uint64_t prev_time = os_gettime_ns() - buffer_time; uint64_t audio_time; os_set_thread_name("audio-io: audio thread"); const char *audio_thread_name = profile_store_name(obs_get_profiler_name_store(), "audio_thread(%s)", audio->info.name); while (os_event_try(audio->stop_event) == EAGAIN) { os_sleep_ms(AUDIO_WAIT_TIME); profile_start(audio_thread_name); pthread_mutex_lock(&audio->line_mutex); audio_time = os_gettime_ns() - buffer_time; audio_time = mix_and_output(audio, audio_time, prev_time); prev_time = audio_time; pthread_mutex_unlock(&audio->line_mutex); profile_end(audio_thread_name); profile_reenable_thread(); } return NULL; }
bool video_output_wait(video_t video) { if (!video) return false; os_event_wait(video->update_event); return os_event_try(video->stop_event) == EAGAIN; }
static void *video_thread(void *param) { struct video_output *video = param; uint64_t cur_time = os_gettime_ns(); while (os_event_try(video->stop_event) == EAGAIN) { /* wait half a frame, update frame */ cur_time += (video->frame_time/2); os_sleepto_ns(cur_time); video->cur_video_time = cur_time; os_event_signal(video->update_event); /* wait another half a frame, swap and output frames */ cur_time += (video->frame_time/2); os_sleepto_ns(cur_time); pthread_mutex_lock(&video->data_mutex); video_swapframes(video); video_output_cur_frame(video); pthread_mutex_unlock(&video->data_mutex); } return NULL; }
static void *reconnect_thread(void *param) { struct obs_output *output = param; unsigned long ms = output->reconnect_retry_sec * 1000; output->reconnect_thread_active = true; if (os_event_timedwait(output->reconnect_stop_event, ms) == ETIMEDOUT) obs_output_start(output); if (os_event_try(output->reconnect_stop_event) == EAGAIN) pthread_detach(output->reconnect_thread); output->reconnect_thread_active = false; return NULL; }
static void *connect_thread(void *data) { struct rtmp_stream *stream = data; int ret = try_connect(stream); if (ret != OBS_OUTPUT_SUCCESS) { obs_output_signal_stop(stream->output, ret); info("Connection to %s failed: %d", stream->path.array, ret); } if (os_event_try(stream->stop_event) == EAGAIN) pthread_detach(stream->connect_thread); stream->connecting = false; return NULL; }
static void *audio_thread(void *param) { struct audio_output *audio = param; size_t rate = audio->info.samples_per_sec; uint64_t samples = 0; uint64_t start_time = os_gettime_ns(); uint64_t prev_time = start_time; uint64_t audio_time = prev_time; uint32_t audio_wait_time = (uint32_t)(audio_frames_to_ns(rate, AUDIO_OUTPUT_FRAMES) / 1000000); os_set_thread_name("audio-io: audio thread"); const char *audio_thread_name = profile_store_name(obs_get_profiler_name_store(), "audio_thread(%s)", audio->info.name); while (os_event_try(audio->stop_event) == EAGAIN) { uint64_t cur_time; os_sleep_ms(audio_wait_time); profile_start(audio_thread_name); cur_time = os_gettime_ns(); while (audio_time <= cur_time) { samples += AUDIO_OUTPUT_FRAMES; audio_time = start_time + audio_frames_to_ns(rate, samples); input_and_output(audio, audio_time, prev_time); prev_time = audio_time; } profile_end(audio_thread_name); profile_reenable_thread(); } return NULL; }
static void *audio_thread(void *param) { struct audio_output *audio = param; uint64_t buffer_time = audio->info.buffer_ms * 1000000; uint64_t prev_time = os_gettime_ns() - buffer_time; uint64_t audio_time; while (os_event_try(audio->stop_event) == EAGAIN) { os_sleep_ms(AUDIO_WAIT_TIME); pthread_mutex_lock(&audio->line_mutex); audio_time = os_gettime_ns() - buffer_time; audio_time = mix_and_output(audio, audio_time, prev_time); prev_time = audio_time; pthread_mutex_unlock(&audio->line_mutex); } return NULL; }
/* * 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; }
static inline bool stopping(struct rtmp_stream *stream) { return os_event_try(stream->stop_event) != EAGAIN; }
/* * Worker thread to get audio data * * Will run until signaled */ static void *pulse_thread(void *vptr) { PULSE_DATA(vptr); if (pulse_connect(data) < 0) return NULL; if (pulse_get_server_info(data) < 0) return NULL; if (pulse_connect_stream(data) < 0) return NULL; if (pulse_skip(data) < 0) return NULL; blog(LOG_DEBUG, "pulse-input: Start recording"); const void *frames; size_t bytes; uint64_t pa_time; int64_t pa_latency; struct source_audio out; out.speakers = data->speakers; out.samples_per_sec = data->samples_per_sec; out.format = pulse_to_obs_audio_format(data->format); while (os_event_try(data->event) == EAGAIN) { pulse_iterate(data); pa_stream_peek(data->stream, &frames, &bytes); // check if we got data if (!bytes) continue; if (!frames) { blog(LOG_DEBUG, "pulse-input: Got audio hole of %u bytes", (unsigned int) bytes); pa_stream_drop(data->stream); continue; } if (pa_stream_get_time(data->stream, &pa_time) < 0) { blog(LOG_ERROR, "pulse-input: Failed to get timing info !"); pa_stream_drop(data->stream); continue; } pulse_get_stream_latency(data->stream, &pa_latency); out.data[0] = (uint8_t *) frames; out.frames = frames_to_bytes(data, bytes); out.timestamp = (pa_time - pa_latency) * 1000; obs_source_output_audio(data->source, &out); pa_stream_drop(data->stream); } pulse_diconnect_stream(data); pulse_disconnect(data); return NULL; }
static inline bool stopping(const struct obs_output *output) { return os_event_try(output->stopping_event) == EAGAIN; }
static inline void socket_thread_windows_internal(struct rtmp_stream *stream) { bool can_write = false; int delay_time; size_t latency_packet_size; uint64_t last_send_time = 0; HANDLE send_backlog_event; OVERLAPPED send_backlog_overlapped; SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); WSAEventSelect(stream->rtmp.m_sb.sb_socket, stream->socket_available_event, FD_READ|FD_WRITE|FD_CLOSE); send_backlog_event = CreateEvent(NULL, true, false, NULL); if (stream->low_latency_mode) { delay_time = 1000 / LATENCY_FACTOR; latency_packet_size = stream->write_buf_size / (LATENCY_FACTOR - 2); } else { latency_packet_size = stream->write_buf_size; delay_time = 0; } if (!stream->disable_send_window_optimization) { memset(&send_backlog_overlapped, 0, sizeof(send_backlog_overlapped)); send_backlog_overlapped.hEvent = send_backlog_event; idealsendbacklognotify(stream->rtmp.m_sb.sb_socket, &send_backlog_overlapped, NULL); } else { blog(LOG_INFO, "socket_thread_windows: Send window " "optimization disabled by user."); } HANDLE objs[3]; objs[0] = stream->socket_available_event; objs[1] = stream->buffer_has_data_event; objs[2] = send_backlog_event; for (;;) { if (os_event_try(stream->send_thread_signaled_exit) != EAGAIN) { pthread_mutex_lock(&stream->write_buf_mutex); if (stream->write_buf_len == 0) { //blog(LOG_DEBUG, "Exiting on empty buffer"); pthread_mutex_unlock(&stream->write_buf_mutex); os_event_reset(stream->send_thread_signaled_exit); break; } pthread_mutex_unlock(&stream->write_buf_mutex); } int status = WaitForMultipleObjects(3, objs, false, INFINITE); if (status == WAIT_ABANDONED || status == WAIT_FAILED) { blog(LOG_ERROR, "socket_thread_windows: Aborting due " "to WaitForMultipleObjects failure"); fatal_sock_shutdown(stream); return; } if (status == WAIT_OBJECT_0) { /* Socket event */ if (!socket_event(stream, &can_write, last_send_time)) return; } else if (status == WAIT_OBJECT_0 + 2) { /* Ideal send backlog event */ ideal_send_backlog_event(stream, &can_write); ResetEvent(send_backlog_event); idealsendbacklognotify(stream->rtmp.m_sb.sb_socket, &send_backlog_overlapped, NULL); continue; } if (can_write) { for (;;) { enum data_ret ret = write_data( stream, &can_write, &last_send_time, latency_packet_size, delay_time); switch (ret) { case RET_BREAK: goto exit_write_loop; case RET_FATAL: return; case RET_CONTINUE:; } } } exit_write_loop:; } if (stream->rtmp.m_sb.sb_socket != INVALID_SOCKET) WSAEventSelect(stream->rtmp.m_sb.sb_socket, stream->socket_available_event, 0); blog(LOG_INFO, "socket_thread_windows: Normal exit"); }
static bool socket_event(struct rtmp_stream *stream, bool *can_write, uint64_t last_send_time) { WSANETWORKEVENTS net_events; bool success; success = !WSAEnumNetworkEvents(stream->rtmp.m_sb.sb_socket, NULL, &net_events); if (!success) { blog(LOG_ERROR, "socket_thread_windows: Aborting due to " "WSAEnumNetworkEvents failure, %d", WSAGetLastError()); fatal_sock_shutdown(stream); return false; } if (net_events.lNetworkEvents & FD_WRITE) *can_write = true; if (net_events.lNetworkEvents & FD_CLOSE) { if (last_send_time) { uint32_t diff = (os_gettime_ns() / 1000000) - last_send_time; blog(LOG_ERROR, "socket_thread_windows: Received " "FD_CLOSE, %u ms since last send " "(buffer: %d / %d)", diff, stream->write_buf_len, stream->write_buf_size); } if (os_event_try(stream->stop_event) != EAGAIN) blog(LOG_ERROR, "socket_thread_windows: Aborting due " "to FD_CLOSE during shutdown, " "%d bytes lost, error %d", stream->write_buf_len, net_events.iErrorCode[FD_CLOSE_BIT]); else blog(LOG_ERROR, "socket_thread_windows: Aborting due " "to FD_CLOSE, error %d", net_events.iErrorCode[FD_CLOSE_BIT]); fatal_sock_shutdown(stream); return false; } if (net_events.lNetworkEvents & FD_READ) { char discard[16384]; int err_code; bool fatal = false; for (;;) { int ret = recv(stream->rtmp.m_sb.sb_socket, discard, sizeof(discard), 0); if (ret == -1) { err_code = WSAGetLastError(); if (err_code == WSAEWOULDBLOCK) break; fatal = true; } else if (ret == 0) { err_code = 0; fatal = true; } if (fatal) { blog(LOG_ERROR, "socket_thread_windows: " "Socket error, recv() returned " "%d, GetLastError() %d", ret, err_code); fatal_sock_shutdown(stream); return false; } } } return true; }