static inline void video_sleep(struct obs_core_video *video, bool raw_active, const bool gpu_active, uint64_t *p_time, uint64_t interval_ns) { struct obs_vframe_info vframe_info; uint64_t cur_time = *p_time; uint64_t t = cur_time + interval_ns; int count; if (os_sleepto_ns(t)) { *p_time = t; count = 1; } else { count = (int)((os_gettime_ns() - cur_time) / interval_ns); *p_time = cur_time + interval_ns * count; } video->total_frames += count; video->lagged_frames += count - 1; vframe_info.timestamp = cur_time; vframe_info.count = count; if (raw_active) circlebuf_push_back(&video->vframe_info_buffer, &vframe_info, sizeof(vframe_info)); if (gpu_active) circlebuf_push_back(&video->vframe_info_buffer_gpu, &vframe_info, sizeof(vframe_info)); }
static void droptest_cap_data_rate(struct rtmp_stream *stream, size_t size) { uint64_t ts = os_gettime_ns(); struct droptest_info info; info.ts = ts; info.size = size; circlebuf_push_back(&stream->droptest_info, &info, sizeof(info)); stream->droptest_size += size; if (stream->droptest_info.size) { circlebuf_peek_front(&stream->droptest_info, &info, sizeof(info)); if (stream->droptest_size > DROPTEST_MAX_BYTES) { uint64_t elapsed = ts - info.ts; if (elapsed < 1000000000ULL) { elapsed = 1000000000ULL - elapsed; os_sleepto_ns(ts + elapsed); } while (stream->droptest_size > DROPTEST_MAX_BYTES) { circlebuf_pop_front(&stream->droptest_info, &info, sizeof(info)); stream->droptest_size -= info.size; } } } }
static inline bool add_packet(struct rtmp_stream *stream, struct encoder_packet *packet) { circlebuf_push_back(&stream->packets, packet, sizeof(struct encoder_packet)); return true; }
static inline bool queue_frame(struct obs_core_video *video, bool raw_active, struct obs_vframe_info *vframe_info, int prev_texture) { bool duplicate = !video->gpu_encoder_avail_queue.size || (video->gpu_encoder_queue.size && vframe_info->count > 1); if (duplicate) { struct obs_tex_frame *tf = circlebuf_data( &video->gpu_encoder_queue, video->gpu_encoder_queue.size - sizeof(*tf)); /* texture-based encoding is stopping */ if (!tf) { return false; } tf->count++; os_sem_post(video->gpu_encode_semaphore); goto finish; } struct obs_tex_frame tf; circlebuf_pop_front(&video->gpu_encoder_avail_queue, &tf, sizeof(tf)); if (tf.released) { gs_texture_acquire_sync(tf.tex, tf.lock_key, GS_WAIT_INFINITE); tf.released = false; } /* the vframe_info->count > 1 case causing a copy can only happen if by * some chance the very first frame has to be duplicated for whatever * reason. otherwise, it goes to the 'duplicate' case above, which * will ensure better performance. */ if (raw_active || vframe_info->count > 1) { gs_copy_texture(tf.tex, video->convert_textures[prev_texture]); } else { gs_texture_t *tex = video->convert_textures[prev_texture]; gs_texture_t *tex_uv = video->convert_uv_textures[prev_texture]; video->convert_textures[prev_texture] = tf.tex; video->convert_uv_textures[prev_texture] = tf.tex_uv; tf.tex = tex; tf.tex_uv = tex_uv; tf.handle = gs_texture_get_shared_handle(tex); } tf.count = 1; tf.timestamp = vframe_info->timestamp; tf.released = true; gs_texture_release_sync(tf.tex, ++tf.lock_key); circlebuf_push_back(&video->gpu_encoder_queue, &tf, sizeof(tf)); os_sem_post(video->gpu_encode_semaphore); finish: return --vframe_info->count; }
static inline void push_back_audio(struct obs_encoder *encoder, struct audio_data *data, size_t size, size_t offset_size) { size -= offset_size; /* push in to the circular buffer */ if (size) for (size_t i = 0; i < encoder->planes; i++) circlebuf_push_back(&encoder->audio_input_buffer[i], data->data[i] + offset_size, size); }
static bool buffer_audio(struct obs_encoder *encoder, struct audio_data *data) { profile_start(buffer_audio_name); size_t samplerate = encoder->samplerate; size_t size = data->frames * encoder->blocksize; size_t offset_size = 0; if (!encoder->start_ts && encoder->paired_encoder) { uint64_t end_ts = data->timestamp; uint64_t v_start_ts = encoder->paired_encoder->start_ts; /* no video yet, so don't start audio */ if (!v_start_ts) goto fail; /* audio starting point still not synced with video starting * point, so don't start audio */ end_ts += (uint64_t)data->frames * 1000000000ULL / samplerate; if (end_ts <= v_start_ts) goto fail; /* ready to start audio, truncate if necessary */ if (data->timestamp < v_start_ts) { uint64_t offset = v_start_ts - data->timestamp; offset = (int)(offset * samplerate / 1000000000); offset_size = (size_t)offset * encoder->blocksize; } encoder->start_ts = v_start_ts; } else if (!encoder->start_ts && !encoder->paired_encoder) { encoder->start_ts = data->timestamp; } size -= offset_size; /* push in to the circular buffer */ if (size) for (size_t i = 0; i < encoder->planes; i++) circlebuf_push_back(&encoder->audio_input_buffer[i], data->data[i] + offset_size, size); profile_end(buffer_audio_name); return true; fail: profile_end(buffer_audio_name); return false; }
static void drop_frames(struct rtmp_stream *stream, const char *name, int highest_priority, int64_t *p_min_dts_usec) { 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_free_encoder_packet(&packet); } } circlebuf_free(&stream->packets); stream->packets = new_buf; if (stream->min_priority < highest_priority) stream->min_priority = highest_priority; *p_min_dts_usec = last_drop_dts_usec; 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 }
void audio_line_output(audio_line_t line, const struct audio_data *data) { if (!line->buffer.size) { line->base_timestamp = data->timestamp; circlebuf_push_back(&line->buffer, data->data, data->frames * line->audio->block_size); } else { uint64_t position = data->timestamp - line->base_timestamp; position = convert_to_sample_offset(line->audio, position); position *= line->audio->block_size; circlebuf_place(&line->buffer, (size_t)position, data->data, data->frames * line->audio->block_size); } }
static inline void video_sleep(struct obs_core_video *video, uint64_t *p_time, uint64_t interval_ns) { struct obs_vframe_info vframe_info; uint64_t cur_time = *p_time; uint64_t t = cur_time + interval_ns; int count; if (os_sleepto_ns(t)) { *p_time = t; count = 1; } else { count = (int)((os_gettime_ns() - cur_time) / interval_ns); *p_time = cur_time + interval_ns * count; } vframe_info.timestamp = cur_time; vframe_info.count = count; circlebuf_push_back(&video->vframe_info_buffer, &vframe_info, sizeof(vframe_info)); }
static void drop_frames(struct rtmp_stream *stream) { struct circlebuf new_buf = {0}; int drop_priority = 0; uint64_t last_drop_dts_usec = 0; int num_frames_dropped = 0; debug("Previous packet count: %d", (int)num_buffered_packets(stream)); 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 == OBS_NAL_PRIORITY_HIGHEST) { circlebuf_push_back(&new_buf, &packet, sizeof(packet)); } else { if (drop_priority < packet.drop_priority) drop_priority = packet.drop_priority; num_frames_dropped++; obs_free_encoder_packet(&packet); } } circlebuf_free(&stream->packets); stream->packets = new_buf; stream->min_priority = drop_priority; stream->min_drop_dts_usec = last_drop_dts_usec; stream->dropped_frames += num_frames_dropped; debug("New packet count: %d", (int)num_buffered_packets(stream)); }
static void drop_frames(struct rtmp_stream *stream) { struct circlebuf new_buf = {0}; int drop_priority = 0; uint64_t last_drop_dts_usec = 0; blog(LOG_DEBUG, "Previous packet count: %d", (int)num_buffered_packets(stream)); 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; if (packet.type == OBS_ENCODER_AUDIO) { circlebuf_push_back(&new_buf, &packet, sizeof(packet)); } else { if (drop_priority < packet.drop_priority) drop_priority = packet.drop_priority; obs_free_encoder_packet(&packet); } } circlebuf_free(&stream->packets); stream->packets = new_buf; stream->min_priority = drop_priority; stream->min_drop_dts_usec = last_drop_dts_usec; blog(LOG_DEBUG, "New packet count: %d", (int)num_buffered_packets(stream)); }
void mp_decode_push_packet(struct mp_decode *decode, AVPacket *packet) { circlebuf_push_back(&decode->packets, packet, sizeof(*packet)); }
static bool process_audio_delay(struct audio_monitor *monitor, float **data, uint32_t *frames, uint64_t ts, uint32_t pad) { obs_source_t *s = monitor->source; uint64_t last_frame_ts = s->last_frame_ts; uint64_t cur_time = os_gettime_ns(); uint64_t front_ts; uint64_t cur_ts; int64_t diff; uint32_t blocksize = monitor->channels * sizeof(float); /* cut off audio if long-since leftover audio in delay buffer */ if (cur_time - monitor->last_recv_time > 1000000000) circlebuf_free(&monitor->delay_buffer); monitor->last_recv_time = cur_time; ts += monitor->source->sync_offset; circlebuf_push_back(&monitor->delay_buffer, &ts, sizeof(ts)); circlebuf_push_back(&monitor->delay_buffer, frames, sizeof(*frames)); circlebuf_push_back(&monitor->delay_buffer, *data, *frames * blocksize); if (!monitor->prev_video_ts) { monitor->prev_video_ts = last_frame_ts; } else if (monitor->prev_video_ts == last_frame_ts) { monitor->time_since_prev += (uint64_t)*frames * 1000000000ULL / (uint64_t)monitor->sample_rate; } else { monitor->time_since_prev = 0; } while (monitor->delay_buffer.size != 0) { size_t size; bool bad_diff; circlebuf_peek_front(&monitor->delay_buffer, &cur_ts, sizeof(ts)); front_ts = cur_ts - ((uint64_t)pad * 1000000000ULL / (uint64_t)monitor->sample_rate); diff = (int64_t)front_ts - (int64_t)last_frame_ts; bad_diff = !last_frame_ts || llabs(diff) > 5000000000 || monitor->time_since_prev > 100000000ULL; /* delay audio if rushing */ if (!bad_diff && diff > 75000000) { #ifdef DEBUG_AUDIO blog(LOG_INFO, "audio rushing, cutting audio, " "diff: %lld, delay buffer size: %lu, " "v: %llu: a: %llu", diff, (int)monitor->delay_buffer.size, last_frame_ts, front_ts); #endif return false; } circlebuf_pop_front(&monitor->delay_buffer, NULL, sizeof(ts)); circlebuf_pop_front(&monitor->delay_buffer, frames, sizeof(*frames)); size = *frames * blocksize; da_resize(monitor->buf, size); circlebuf_pop_front(&monitor->delay_buffer, monitor->buf.array, size); /* cut audio if dragging */ if (!bad_diff && diff < -75000000 && monitor->delay_buffer.size > 0) { #ifdef DEBUG_AUDIO blog(LOG_INFO, "audio dragging, cutting audio, " "diff: %lld, delay buffer size: %lu, " "v: %llu: a: %llu", diff, (int)monitor->delay_buffer.size, last_frame_ts, front_ts); #endif continue; } *data = monitor->buf.array; return true; } return false; }
bool audio_callback(void *param, uint64_t start_ts_in, uint64_t end_ts_in, uint64_t *out_ts, uint32_t mixers, struct audio_output_data *mixes) { struct obs_core_data *data = &obs->data; struct obs_core_audio *audio = &obs->audio; struct obs_source *source; size_t sample_rate = audio_output_get_sample_rate(audio->audio); size_t channels = audio_output_get_channels(audio->audio); struct ts_info ts = {start_ts_in, end_ts_in}; size_t audio_size; uint64_t min_ts; da_resize(audio->render_order, 0); da_resize(audio->root_nodes, 0); circlebuf_push_back(&audio->buffered_timestamps, &ts, sizeof(ts)); circlebuf_peek_front(&audio->buffered_timestamps, &ts, sizeof(ts)); min_ts = ts.start; audio_size = AUDIO_OUTPUT_FRAMES * sizeof(float); #if DEBUG_AUDIO == 1 blog(LOG_DEBUG, "ts %llu-%llu", ts.start, ts.end); #endif /* ------------------------------------------------ */ /* build audio render order * NOTE: these are source channels, not audio channels */ for (uint32_t i = 0; i < MAX_CHANNELS; i++) { obs_source_t *source = obs_get_output_source(i); if (source) { obs_source_enum_active_tree(source, push_audio_tree, audio); push_audio_tree(NULL, source, audio); da_push_back(audio->root_nodes, &source); obs_source_release(source); } } pthread_mutex_lock(&data->audio_sources_mutex); source = data->first_audio_source; while (source) { push_audio_tree(NULL, source, audio); source = (struct obs_source*)source->next_audio_source; } pthread_mutex_unlock(&data->audio_sources_mutex); /* ------------------------------------------------ */ /* render audio data */ for (size_t i = 0; i < audio->render_order.num; i++) { obs_source_t *source = audio->render_order.array[i]; obs_source_audio_render(source, mixers, channels, sample_rate, audio_size); } /* ------------------------------------------------ */ /* get minimum audio timestamp */ pthread_mutex_lock(&data->audio_sources_mutex); calc_min_ts(data, sample_rate, &min_ts); pthread_mutex_unlock(&data->audio_sources_mutex); /* ------------------------------------------------ */ /* if a source has gone backward in time, buffer */ if (min_ts < ts.start) add_audio_buffering(audio, sample_rate, &ts, min_ts); /* ------------------------------------------------ */ /* mix audio */ if (!audio->buffering_wait_ticks) { for (size_t i = 0; i < audio->root_nodes.num; i++) { obs_source_t *source = audio->root_nodes.array[i]; if (source->audio_pending) continue; pthread_mutex_lock(&source->audio_buf_mutex); if (source->audio_output_buf[0][0] && source->audio_ts) mix_audio(mixes, source, channels, sample_rate, &ts); pthread_mutex_unlock(&source->audio_buf_mutex); } } /* ------------------------------------------------ */ /* discard audio */ pthread_mutex_lock(&data->audio_sources_mutex); source = data->first_audio_source; while (source) { pthread_mutex_lock(&source->audio_buf_mutex); discard_audio(audio, source, channels, sample_rate, &ts); pthread_mutex_unlock(&source->audio_buf_mutex); source = (struct obs_source*)source->next_audio_source; } pthread_mutex_unlock(&data->audio_sources_mutex); /* ------------------------------------------------ */ /* release audio sources */ release_audio_sources(audio); circlebuf_pop_front(&audio->buffered_timestamps, NULL, sizeof(ts)); *out_ts = ts.start; if (audio->buffering_wait_ticks) { audio->buffering_wait_ticks--; return false; } UNUSED_PARAMETER(param); return true; }