static void rtmp_stream_data(void *data, struct encoder_packet *packet) { struct rtmp_stream *stream = data; struct encoder_packet new_packet; bool added_packet = false; if (disconnected(stream) || !active(stream)) return; if (packet->type == OBS_ENCODER_VIDEO) obs_parse_avc_packet(&new_packet, packet); else obs_encoder_packet_ref(&new_packet, packet); pthread_mutex_lock(&stream->packets_mutex); if (!disconnected(stream)) { added_packet = (packet->type == OBS_ENCODER_VIDEO) ? add_video_packet(stream, &new_packet) : add_packet(stream, &new_packet); } pthread_mutex_unlock(&stream->packets_mutex); if (added_packet) os_sem_post(stream->send_sem); else obs_encoder_packet_release(&new_packet); }
static void interleave_packets(void *data, struct encoder_packet *packet) { struct obs_output *output = data; struct encoder_packet out; bool was_started; if (!active(output)) return; if (packet->type == OBS_ENCODER_AUDIO) packet->track_idx = get_track_index(output, packet); pthread_mutex_lock(&output->interleaved_mutex); /* if first video frame is not a keyframe, discard until received */ if (!output->received_video && packet->type == OBS_ENCODER_VIDEO && !packet->keyframe) { discard_unused_audio_packets(output, packet->dts_usec); pthread_mutex_unlock(&output->interleaved_mutex); if (output->active_delay_ns) obs_encoder_packet_release(packet); return; } was_started = output->received_audio && output->received_video; if (output->active_delay_ns) out = *packet; else obs_encoder_packet_create_instance(&out, packet); if (was_started) apply_interleaved_packet_offset(output, &out); else check_received(output, packet); insert_interleaved_packet(output, &out); set_higher_ts(output, &out); /* when both video and audio have been received, we're ready * to start sending out packets (one at a time) */ if (output->received_audio && output->received_video) { if (!was_started) { if (prune_interleaved_packets(output)) { if (initialize_interleaved_packets(output)) { resort_interleaved_packets(output); send_interleaved(output); } } } else { send_interleaved(output); } } pthread_mutex_unlock(&output->interleaved_mutex); }
static void discard_to_idx(struct obs_output *output, size_t idx) { for (size_t i = 0; i < idx; i++) { struct encoder_packet *packet = &output->interleaved_packets.array[i]; obs_encoder_packet_release(packet); } da_erase_range(output->interleaved_packets, 0, idx); }
static void drop_frames(struct rtmp_stream *stream, const char *name, int highest_priority, bool pframes) { struct circlebuf new_buf = {0}; uint64_t last_drop_dts_usec = 0; int num_frames_dropped = 0; #ifdef _DEBUG int start_packets = (int)num_buffered_packets(stream); #else UNUSED_PARAMETER(name); #endif circlebuf_reserve(&new_buf, sizeof(struct encoder_packet) * 8); while (stream->packets.size) { struct encoder_packet packet; circlebuf_pop_front(&stream->packets, &packet, sizeof(packet)); last_drop_dts_usec = packet.dts_usec; /* do not drop audio data or video keyframes */ if (packet.type == OBS_ENCODER_AUDIO || packet.drop_priority >= highest_priority) { circlebuf_push_back(&new_buf, &packet, sizeof(packet)); } else { num_frames_dropped++; obs_encoder_packet_release(&packet); } } circlebuf_free(&stream->packets); stream->packets = new_buf; if (stream->min_priority < highest_priority) stream->min_priority = highest_priority; if (!num_frames_dropped) return; stream->dropped_frames += num_frames_dropped; #ifdef _DEBUG debug("Dropped %s, prev packet count: %d, new packet count: %d", name, start_packets, (int)num_buffered_packets(stream)); #endif }
static int write_packet(struct flv_output *stream, struct encoder_packet *packet, bool is_header) { uint8_t *data; size_t size; int ret = 0; stream->last_packet_ts = get_ms_time(packet, packet->dts); flv_packet_mux(packet, &data, &size, is_header); fwrite(data, 1, size, stream->file); bfree(data); obs_encoder_packet_release(packet); return ret; }
static inline void send_interleaved(struct obs_output *output) { struct encoder_packet out = output->interleaved_packets.array[0]; /* do not send an interleaved packet if there's no packet of the * opposing type of a higher timstamp in the interleave buffer. * this ensures that the timestamps are monotonic */ if (!has_higher_opposing_ts(output, &out)) return; if (out.type == OBS_ENCODER_VIDEO) output->total_frames++; da_erase(output->interleaved_packets, 0); output->info.encoded_packet(output->context.data, &out); obs_encoder_packet_release(&out); }
static void default_encoded_callback(void *param, struct encoder_packet *packet) { struct obs_output *output = param; if (data_active(output)) { if (packet->type == OBS_ENCODER_AUDIO) packet->track_idx = get_track_index(output, packet); output->info.encoded_packet(output->context.data, packet); if (packet->type == OBS_ENCODER_VIDEO) output->total_frames++; } if (output->active_delay_ns) obs_encoder_packet_release(packet); }
static inline void free_packets(struct rtmp_stream *stream) { size_t num_packets; pthread_mutex_lock(&stream->packets_mutex); num_packets = num_buffered_packets(stream); if (num_packets) info("Freeing %d remaining packets", (int)num_packets); while (stream->packets.size) { struct encoder_packet packet; circlebuf_pop_front(&stream->packets, &packet, sizeof(packet)); obs_encoder_packet_release(&packet); } pthread_mutex_unlock(&stream->packets_mutex); }
static void flv_output_data(void *data, struct encoder_packet *packet) { struct flv_output *stream = data; struct encoder_packet parsed_packet; if (!stream->sent_headers) { write_headers(stream); stream->sent_headers = true; } if (packet->type == OBS_ENCODER_VIDEO) { obs_parse_avc_packet(&parsed_packet, packet); write_packet(stream, &parsed_packet, false); obs_encoder_packet_release(&parsed_packet); } else { write_packet(stream, packet, false); } }
static int send_packet(struct rtmp_stream *stream, struct encoder_packet *packet, bool is_header, size_t idx) { uint8_t *data; size_t size; int recv_size = 0; int ret = 0; if (!stream->new_socket_loop) { #ifdef _WIN32 ret = ioctlsocket(stream->rtmp.m_sb.sb_socket, FIONREAD, (u_long*)&recv_size); #else ret = ioctl(stream->rtmp.m_sb.sb_socket, FIONREAD, &recv_size); #endif if (ret >= 0 && recv_size > 0) { if (!discard_recv_data(stream, (size_t)recv_size)) return -1; } } flv_packet_mux(packet, &data, &size, is_header); #ifdef TEST_FRAMEDROPS droptest_cap_data_rate(stream, size); #endif ret = RTMP_Write(&stream->rtmp, (char*)data, (int)size, (int)idx); bfree(data); if (is_header) bfree(packet->data); else obs_encoder_packet_release(packet); stream->total_bytes_sent += size; return ret; }
static inline void free_packets(struct obs_output *output) { for (size_t i = 0; i < output->interleaved_packets.num; i++) obs_encoder_packet_release(output->interleaved_packets.array+i); da_free(output->interleaved_packets); }
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_encoder_packet_release(&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"); } if (stream->new_socket_loop) { os_event_signal(stream->send_thread_signaled_exit); os_event_signal(stream->buffer_has_data_event); pthread_join(stream->socket_thread, NULL); stream->socket_thread_active = false; stream->rtmp.m_bCustomSend = false; } set_output_error(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; }