static void output_reconnect(struct obs_output *output) { int ret; if (!output->reconnecting) output->reconnect_retries = 0; if (output->reconnect_retries >= output->reconnect_retry_max) { output->reconnecting = false; signal_stop(output, OBS_OUTPUT_DISCONNECTED); return; } if (!output->reconnecting) { output->reconnecting = true; os_event_reset(output->reconnect_stop_event); } output->reconnect_retries++; ret = pthread_create(&output->reconnect_thread, NULL, &reconnect_thread, output); if (ret < 0) { blog(LOG_WARNING, "Failed to create reconnect thread"); output->reconnecting = false; signal_stop(output, OBS_OUTPUT_DISCONNECTED); } else { blog(LOG_INFO, "Output '%s': Reconnecting in %d seconds..", output->context.name, output->reconnect_retry_sec); signal_reconnect(output); } }
/****************************************************************//** Wait for a work item to appear in the queue. @return work item */ UNIV_INTERN void* ib_wqueue_wait( /*===========*/ ib_wqueue_t* wq) /*!< in: work queue */ { ib_list_node_t* node; for (;;) { os_event_wait(wq->event); mutex_enter(&wq->mutex); node = ib_list_get_first(wq->items); if (node) { ib_list_remove(wq->items, node); if (!ib_list_get_first(wq->items)) { /* We must reset the event when the list gets emptied. */ os_event_reset(wq->event); } break; } mutex_exit(&wq->mutex); } mutex_exit(&wq->mutex); return(node->data); }
static void *send_thread(void *data) { struct rtmp_stream *stream = data; os_set_thread_name("rtmp-stream: send_thread"); while (os_sem_wait(stream->send_sem) == 0) { struct encoder_packet packet; if (stopping(stream) && stream->stop_ts == 0) { break; } if (!get_next_packet(stream, &packet)) continue; if (stopping(stream)) { if (can_shutdown_stream(stream, &packet)) { obs_free_encoder_packet(&packet); break; } } if (!stream->sent_headers) { if (!send_headers(stream)) { os_atomic_set_bool(&stream->disconnected, true); break; } } if (send_packet(stream, &packet, false, packet.track_idx) < 0) { os_atomic_set_bool(&stream->disconnected, true); break; } } if (disconnected(stream)) { info("Disconnected from %s", stream->path.array); } else { info("User stopped the stream"); } RTMP_Close(&stream->rtmp); if (!stopping(stream)) { pthread_detach(stream->send_thread); obs_output_signal_stop(stream->output, OBS_OUTPUT_DISCONNECTED); } else { obs_output_end_data_capture(stream->output); } free_packets(stream); os_event_reset(stream->stop_event); os_atomic_set_bool(&stream->active, false); stream->sent_headers = false; return NULL; }
/*********************************************************************** Puts the cell event in reset state. */ static ib_longlong sync_cell_event_reset( /*==================*/ /* out: value of signal_count at the time of reset. */ ulint type, /* in: lock type mutex/rw_lock */ void* object) /* in: the rw_lock/mutex object */ { if (type == SYNC_MUTEX) { return(os_event_reset(((mutex_t *) object)->event)); #ifdef __WIN__ } else if (type == RW_LOCK_WAIT_EX) { return(os_event_reset( ((rw_lock_t *) object)->wait_ex_event)); #endif } else { return(os_event_reset(((rw_lock_t *) object)->event)); } }
static void output_reconnect(struct obs_output *output) { int ret; if (!reconnecting(output)) { output->reconnect_retry_cur_sec = output->reconnect_retry_sec; output->reconnect_retries = 0; } if (output->reconnect_retries >= output->reconnect_retry_max) { output->stop_code = OBS_OUTPUT_DISCONNECTED; os_atomic_set_bool(&output->reconnecting, false); if (delay_active(output)) os_atomic_set_bool(&output->delay_active, false); obs_output_end_data_capture(output); return; } if (!reconnecting(output)) { os_atomic_set_bool(&output->reconnecting, true); os_event_reset(output->reconnect_stop_event); } if (output->reconnect_retries) { output->reconnect_retry_cur_sec *= 2; if (output->reconnect_retry_cur_sec > MAX_RETRY_SEC) output->reconnect_retry_cur_sec = MAX_RETRY_SEC; } output->reconnect_retries++; output->stop_code = OBS_OUTPUT_DISCONNECTED; ret = pthread_create(&output->reconnect_thread, NULL, &reconnect_thread, output); if (ret < 0) { blog(LOG_WARNING, "Failed to create reconnect thread"); os_atomic_set_bool(&output->reconnecting, false); } else { blog(LOG_INFO, "Output '%s': Reconnecting in %d seconds..", output->context.name, output->reconnect_retry_sec); signal_reconnect(output); } }
void rw_lock_debug_mutex_enter(void) /*==========================*/ { loop: if (0 == mutex_enter_nowait(&rw_lock_debug_mutex)) { return; } os_event_reset(rw_lock_debug_event); rw_lock_debug_waiters = TRUE; if (0 == mutex_enter_nowait(&rw_lock_debug_mutex)) { return; } os_event_wait(rw_lock_debug_event); goto loop; }
static void rtmp_stream_stop(void *data) { struct rtmp_stream *stream = data; void *ret; os_event_signal(stream->stop_event); if (stream->connecting) pthread_join(stream->connect_thread, &ret); if (stream->active) { obs_output_end_data_capture(stream->output); os_sem_post(stream->send_sem); pthread_join(stream->send_thread, &ret); RTMP_Close(&stream->rtmp); } os_event_reset(stream->stop_event); stream->sent_headers = false; }
void obs_output_actual_stop(obs_output_t *output, bool force, uint64_t ts) { bool call_stop = true; bool was_reconnecting = false; if (stopping(output)) return; os_event_reset(output->stopping_event); was_reconnecting = reconnecting(output) && !delay_active(output); if (reconnecting(output)) { os_event_signal(output->reconnect_stop_event); if (output->reconnect_thread_active) pthread_join(output->reconnect_thread, NULL); } if (force) { if (delay_active(output)) { call_stop = delay_capturing(output); os_atomic_set_bool(&output->delay_active, false); os_atomic_set_bool(&output->delay_capturing, false); output->stop_code = OBS_OUTPUT_SUCCESS; obs_output_end_data_capture(output); os_event_signal(output->stopping_event); } else { call_stop = data_active(output); } } else { call_stop = data_active(output); } if (output->context.data && call_stop) { output->info.stop(output->context.data, ts); } else if (was_reconnecting) { output->stop_code = OBS_OUTPUT_SUCCESS; signal_stop(output); os_event_signal(output->stopping_event); } }
/******************************************************************//** Reserves a wait array cell for waiting for an object. The event of the cell is reset to nonsignalled state. */ UNIV_INTERN void sync_array_reserve_cell( /*====================*/ sync_array_t* arr, /*!< in: wait array */ void* object, /*!< in: pointer to the object to wait for */ ulint type, /*!< in: lock request type */ const char* file, /*!< in: file where requested */ ulint line, /*!< in: line where requested */ ulint* index) /*!< out: index of the reserved cell */ { sync_cell_t* cell; os_event_t event; ulint i; ut_a(object); ut_a(index); sync_array_enter(arr); arr->res_count++; /* Reserve a new cell. */ for (i = 0; i < arr->n_cells; i++) { cell = sync_array_get_nth_cell(arr, i); if (cell->wait_object == NULL) { cell->waiting = FALSE; cell->wait_object = object; if (type == SYNC_MUTEX) { cell->old_wait_mutex = object; } else { cell->old_wait_rw_lock = object; } cell->request_type = type; cell->file = file; cell->line = line; arr->n_reserved++; *index = i; sync_array_exit(arr); /* Make sure the event is reset and also store the value of signal_count at which the event was reset. */ event = sync_cell_get_event(cell); cell->signal_count = os_event_reset(event); cell->reservation_time = time(NULL); cell->thread = os_thread_get_curr_id(); return; } } ut_error; /* No free cell found */ return; }
/************************************************************************ Writes a page asynchronously from the buffer buf_pool to a file, if it can be found in the buf_pool and it is in a flushable state. NOTE: in simulated aio we must call os_aio_simulated_wake_handler_threads after we have posted a batch of writes! */ static ulint buf_flush_try_page( /*===============*/ /* out: 1 if a page was flushed, 0 otherwise */ ulint space, /* in: space id */ ulint offset, /* in: page offset */ ulint flush_type) /* in: BUF_FLUSH_LRU, BUF_FLUSH_LIST, or BUF_FLUSH_SINGLE_PAGE */ { buf_block_t* block; ibool locked; ut_ad(flush_type == BUF_FLUSH_LRU || flush_type == BUF_FLUSH_LIST || flush_type == BUF_FLUSH_SINGLE_PAGE); mutex_enter(&(buf_pool->mutex)); block = buf_page_hash_get(space, offset); ut_a(!block || block->state == BUF_BLOCK_FILE_PAGE); if (!block) { mutex_exit(&(buf_pool->mutex)); return(0); } mutex_enter(&block->mutex); if (flush_type == BUF_FLUSH_LIST && buf_flush_ready_for_flush(block, flush_type)) { block->io_fix = BUF_IO_WRITE; /* If AWE is enabled and the page is not mapped to a frame, then map it */ if (block->frame == NULL) { ut_a(srv_use_awe); /* We set second parameter TRUE because the block is in the LRU list and we must put it to awe_LRU_free_mapped list once mapped to a frame */ buf_awe_map_page_to_frame(block, TRUE); } block->flush_type = flush_type; if (buf_pool->n_flush[flush_type] == 0) { os_event_reset(buf_pool->no_flush[flush_type]); } (buf_pool->n_flush[flush_type])++; locked = FALSE; /* If the simulated aio thread is not running, we must not wait for any latch, as we may end up in a deadlock: if buf_fix_count == 0, then we know we need not wait */ if (block->buf_fix_count == 0) { rw_lock_s_lock_gen(&(block->lock), BUF_IO_WRITE); locked = TRUE; } mutex_exit(&block->mutex); mutex_exit(&(buf_pool->mutex)); if (!locked) { buf_flush_buffered_writes(); rw_lock_s_lock_gen(&(block->lock), BUF_IO_WRITE); } #ifdef UNIV_DEBUG if (buf_debug_prints) { fprintf(stderr, "Flushing page space %lu, page no %lu \n", (ulong) block->space, (ulong) block->offset); } #endif /* UNIV_DEBUG */ buf_flush_write_block_low(block); return(1); } else if (flush_type == BUF_FLUSH_LRU && buf_flush_ready_for_flush(block, flush_type)) { /* VERY IMPORTANT: Because any thread may call the LRU flush, even when owning locks on pages, to avoid deadlocks, we must make sure that the s-lock is acquired on the page without waiting: this is accomplished because in the if-condition above we require the page not to be bufferfixed (in function ..._ready_for_flush). */ block->io_fix = BUF_IO_WRITE; /* If AWE is enabled and the page is not mapped to a frame, then map it */ if (block->frame == NULL) { ut_a(srv_use_awe); /* We set second parameter TRUE because the block is in the LRU list and we must put it to awe_LRU_free_mapped list once mapped to a frame */ buf_awe_map_page_to_frame(block, TRUE); } block->flush_type = flush_type; if (buf_pool->n_flush[flush_type] == 0) { os_event_reset(buf_pool->no_flush[flush_type]); } (buf_pool->n_flush[flush_type])++; rw_lock_s_lock_gen(&(block->lock), BUF_IO_WRITE); /* Note that the s-latch is acquired before releasing the buf_pool mutex: this ensures that the latch is acquired immediately. */ mutex_exit(&block->mutex); mutex_exit(&(buf_pool->mutex)); buf_flush_write_block_low(block); return(1); } else if (flush_type == BUF_FLUSH_SINGLE_PAGE && buf_flush_ready_for_flush(block, flush_type)) { block->io_fix = BUF_IO_WRITE; /* If AWE is enabled and the page is not mapped to a frame, then map it */ if (block->frame == NULL) { ut_a(srv_use_awe); /* We set second parameter TRUE because the block is in the LRU list and we must put it to awe_LRU_free_mapped list once mapped to a frame */ buf_awe_map_page_to_frame(block, TRUE); } block->flush_type = flush_type; if (buf_pool->n_flush[block->flush_type] == 0) { os_event_reset(buf_pool->no_flush[block->flush_type]); } (buf_pool->n_flush[flush_type])++; mutex_exit(&block->mutex); mutex_exit(&(buf_pool->mutex)); rw_lock_s_lock_gen(&(block->lock), BUF_IO_WRITE); #ifdef UNIV_DEBUG if (buf_debug_prints) { fprintf(stderr, "Flushing single page space %lu," " page no %lu \n", (ulong) block->space, (ulong) block->offset); } #endif /* UNIV_DEBUG */ buf_flush_write_block_low(block); return(1); } mutex_exit(&block->mutex); mutex_exit(&(buf_pool->mutex)); return(0); }
static int init_send(struct rtmp_stream *stream) { int ret; size_t idx = 0; bool next = true; #if defined(_WIN32) adjust_sndbuf_size(stream, MIN_SENDBUF_SIZE); #endif reset_semaphore(stream); ret = pthread_create(&stream->send_thread, NULL, send_thread, stream); if (ret != 0) { RTMP_Close(&stream->rtmp); warn("Failed to create send thread"); return OBS_OUTPUT_ERROR; } if (stream->new_socket_loop) { int one = 1; #ifdef _WIN32 if (ioctlsocket(stream->rtmp.m_sb.sb_socket, FIONBIO, &one)) { stream->rtmp.last_error_code = WSAGetLastError(); #else if (ioctl(stream->rtmp.m_sb.sb_socket, FIONBIO, &one)) { stream->rtmp.last_error_code = errno; #endif warn("Failed to set non-blocking socket"); return OBS_OUTPUT_ERROR; } os_event_reset(stream->send_thread_signaled_exit); info("New socket loop enabled by user"); if (stream->low_latency_mode) info("Low latency mode enabled by user"); if (stream->write_buf) bfree(stream->write_buf); int total_bitrate = 0; obs_output_t *context = stream->output; obs_encoder_t *vencoder = obs_output_get_video_encoder(context); if (vencoder) { obs_data_t *params = obs_encoder_get_settings(vencoder); if (params) { int bitrate = obs_data_get_int(params, "bitrate"); total_bitrate += bitrate; obs_data_release(params); } } obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, 0); if (aencoder) { obs_data_t *params = obs_encoder_get_settings(aencoder); if (params) { int bitrate = obs_data_get_int(params, "bitrate"); total_bitrate += bitrate; obs_data_release(params); } } // to bytes/sec int ideal_buffer_size = total_bitrate * 128; if (ideal_buffer_size < 131072) ideal_buffer_size = 131072; stream->write_buf_size = ideal_buffer_size; stream->write_buf = bmalloc(ideal_buffer_size); #ifdef _WIN32 ret = pthread_create(&stream->socket_thread, NULL, socket_thread_windows, stream); #else warn("New socket loop not supported on this platform"); return OBS_OUTPUT_ERROR; #endif if (ret != 0) { RTMP_Close(&stream->rtmp); warn("Failed to create socket thread"); return OBS_OUTPUT_ERROR; } stream->socket_thread_active = true; stream->rtmp.m_bCustomSend = true; stream->rtmp.m_customSendFunc = socket_queue_data; stream->rtmp.m_customSendParam = stream; } os_atomic_set_bool(&stream->active, true); while (next) { if (!send_meta_data(stream, idx++, &next)) { warn("Disconnected while attempting to connect to " "server."); set_output_error(stream); return OBS_OUTPUT_DISCONNECTED; } } obs_output_begin_data_capture(stream->output, 0); return OBS_OUTPUT_SUCCESS; } #ifdef _WIN32 static void win32_log_interface_type(struct rtmp_stream *stream) { RTMP *rtmp = &stream->rtmp; MIB_IPFORWARDROW route; uint32_t dest_addr, source_addr; char hostname[256]; HOSTENT *h; if (rtmp->Link.hostname.av_len >= sizeof(hostname) - 1) return; strncpy(hostname, rtmp->Link.hostname.av_val, sizeof(hostname)); hostname[rtmp->Link.hostname.av_len] = 0; h = gethostbyname(hostname); if (!h) return; dest_addr = *(uint32_t*)h->h_addr_list[0]; if (rtmp->m_bindIP.addrLen == 0) source_addr = 0; else if (rtmp->m_bindIP.addr.ss_family == AF_INET) source_addr = (*(struct sockaddr_in*)&rtmp->m_bindIP) .sin_addr.S_un.S_addr; else return; if (!GetBestRoute(dest_addr, source_addr, &route)) { MIB_IFROW row; memset(&row, 0, sizeof(row)); row.dwIndex = route.dwForwardIfIndex; if (!GetIfEntry(&row)) { uint32_t speed =row.dwSpeed / 1000000; char *type; struct dstr other = {0}; if (row.dwType == IF_TYPE_ETHERNET_CSMACD) { type = "ethernet"; } else if (row.dwType == IF_TYPE_IEEE80211) { type = "802.11"; } else { dstr_printf(&other, "type %lu", row.dwType); type = other.array; } info("Interface: %s (%s, %lu mbps)", row.bDescr, type, speed); dstr_free(&other); } } }
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"); }