(API Change) libobs: Fix output data cutoff on stop

(Note: This commit also modifies obs-ffmpeg and obs-outputs)

API Changed:
obs_output_info::void (*stop)(void *data);

To:
obs_output_info::void (*stop)(void *data, uint64_t ts);

This fixes the long-time design flaw where obs_output_stop and the
output 'stop' callback would just shut down the output without
considering the timing of when obs_output_stop was used, discarding any
possible buffering and causing the output to get cut off at an
unexpected timing.

The 'stop' callback of obs_output_info now takes a timestamp with the
expectation that the output will use that timestamp to stop output data
in accordance to that timing.  obs_output_stop now records the timestamp
at the time that the function is called and calls the 'stop' callback
with that timestamp.  If needed, obs_output_force_stop will still stop
the output immediately without buffering.
This commit is contained in:
jp9000
2016-06-11 11:42:29 -07:00
parent 29e849e355
commit d7db0b8b01
8 changed files with 270 additions and 85 deletions

View File

@@ -19,6 +19,7 @@
#include <obs-avc.h>
#include <util/dstr.h>
#include <util/pipe.h>
#include <util/threading.h>
#include "ffmpeg-mux/ffmpeg-mux.h"
#include <libavformat/avformat.h>
@@ -33,10 +34,12 @@
struct ffmpeg_muxer {
obs_output_t *output;
os_process_pipe_t *pipe;
int64_t stop_ts;
struct dstr path;
bool sent_headers;
bool active;
bool capturing;
volatile bool active;
volatile bool stopping;
volatile bool capturing;
};
static const char *ffmpeg_mux_getname(void *unused)
@@ -72,6 +75,21 @@ static void *ffmpeg_mux_create(obs_data_t *settings, obs_output_t *output)
#define FFMPEG_MUX "ffmpeg-mux"
#endif
static inline bool capturing(struct ffmpeg_muxer *stream)
{
return os_atomic_load_bool(&stream->capturing);
}
static inline bool stopping(struct ffmpeg_muxer *stream)
{
return os_atomic_load_bool(&stream->stopping);
}
static inline bool active(struct ffmpeg_muxer *stream)
{
return os_atomic_load_bool(&stream->active);
}
/* TODO: allow codecs other than h264 whenever we start using them */
static void add_video_encoder_params(struct ffmpeg_muxer *stream,
@@ -223,8 +241,8 @@ static bool ffmpeg_mux_start(void *data)
}
/* write headers and start capture */
stream->active = true;
stream->capturing = true;
os_atomic_set_bool(&stream->active, true);
os_atomic_set_bool(&stream->capturing, true);
obs_output_begin_data_capture(stream->output, 0);
info("Writing file '%s'...", stream->path.array);
@@ -235,29 +253,32 @@ static int deactivate(struct ffmpeg_muxer *stream)
{
int ret = -1;
if (stream->active) {
if (active(stream)) {
ret = os_process_pipe_destroy(stream->pipe);
stream->pipe = NULL;
stream->active = false;
stream->sent_headers = false;
os_atomic_set_bool(&stream->active, false);
os_atomic_set_bool(&stream->sent_headers, false);
info("Output of file '%s' stopped", stream->path.array);
}
if (stopping(stream))
obs_output_end_data_capture(stream->output);
os_atomic_set_bool(&stream->stopping, false);
return ret;
}
static void ffmpeg_mux_stop(void *data)
static void ffmpeg_mux_stop(void *data, uint64_t ts)
{
struct ffmpeg_muxer *stream = data;
if (stream->capturing) {
obs_output_end_data_capture(stream->output);
stream->capturing = false;
if (capturing(stream)) {
stream->stop_ts = (int64_t)ts / 1000LL;
os_atomic_set_bool(&stream->stopping, true);
os_atomic_set_bool(&stream->capturing, false);
}
deactivate(stream);
}
static void signal_failure(struct ffmpeg_muxer *stream)
@@ -271,7 +292,7 @@ static void signal_failure(struct ffmpeg_muxer *stream)
}
obs_output_signal_stop(stream->output, code);
stream->capturing = false;
os_atomic_set_bool(&stream->capturing, false);
}
static bool write_packet(struct ffmpeg_muxer *stream,
@@ -358,7 +379,7 @@ static void ffmpeg_mux_data(void *data, struct encoder_packet *packet)
{
struct ffmpeg_muxer *stream = data;
if (!stream->active)
if (!active(stream))
return;
if (!stream->sent_headers) {
@@ -368,6 +389,13 @@ static void ffmpeg_mux_data(void *data, struct encoder_packet *packet)
stream->sent_headers = true;
}
if (stopping(stream)) {
if (packet->sys_dts_usec >= stream->stop_ts) {
deactivate(stream);
return;
}
}
write_packet(stream, packet);
}

View File

@@ -89,6 +89,11 @@ struct ffmpeg_output {
bool connecting;
pthread_t start_thread;
uint64_t audio_start_ts;
uint64_t video_start_ts;
uint64_t stop_ts;
volatile bool stopping;
bool write_thread_active;
pthread_mutex_t write_mutex;
pthread_t write_thread;
@@ -549,6 +554,11 @@ fail:
/* ------------------------------------------------------------------------- */
static inline bool stopping(struct ffmpeg_output *output)
{
return os_atomic_load_bool(&output->stopping);
}
static const char *ffmpeg_output_getname(void *unused)
{
UNUSED_PARAMETER(unused);
@@ -589,7 +599,7 @@ fail:
return NULL;
}
static void ffmpeg_output_stop(void *data);
static void ffmpeg_output_full_stop(void *data);
static void ffmpeg_deactivate(struct ffmpeg_output *output);
static void ffmpeg_output_destroy(void *data)
@@ -600,7 +610,7 @@ static void ffmpeg_output_destroy(void *data)
if (output->connecting)
pthread_join(output->start_thread, NULL);
ffmpeg_output_stop(output);
ffmpeg_output_full_stop(output);
pthread_mutex_destroy(&output->write_mutex);
os_sem_destroy(output->write_sem);
@@ -648,6 +658,8 @@ static void receive_video(void *param, struct video_data *frame)
av_init_packet(&packet);
if (!output->video_start_ts)
output->video_start_ts = frame->timestamp;
if (!data->start_timestamp)
data->start_timestamp = frame->timestamp;
@@ -769,6 +781,8 @@ static bool prepare_audio(struct ffmpeg_data *data,
return false;
cutoff = data->start_timestamp - frame->timestamp;
output->timestamp += cutoff;
cutoff = cutoff * (uint64_t)data->audio_samplerate /
1000000000;
@@ -798,6 +812,9 @@ static void receive_audio(void *param, struct audio_data *frame)
if (!prepare_audio(data, frame, &in))
return;
if (!output->audio_start_ts)
output->audio_start_ts = in.timestamp;
frame_size_bytes = (size_t)data->frame_size * data->audio_size;
for (size_t i = 0; i < data->audio_planes; i++)
@@ -813,6 +830,26 @@ static void receive_audio(void *param, struct audio_data *frame)
}
}
static uint64_t get_packet_sys_dts(struct ffmpeg_output *output,
AVPacket *packet)
{
struct ffmpeg_data *data = &output->ff_data;
uint64_t start_ts;
AVRational time_base;
if (data->video && data->video->index == packet->stream_index) {
time_base = data->video->time_base;
start_ts = output->video_start_ts;
} else {
time_base = data->audio->time_base;
start_ts = output->audio_start_ts;
}
return start_ts + (uint64_t)av_rescale_q(packet->dts,
time_base, (AVRational){1, 1000000000});
}
static int process_packet(struct ffmpeg_output *output)
{
AVPacket packet;
@@ -835,6 +872,14 @@ static int process_packet(struct ffmpeg_output *output)
packet.size, packet.flags,
packet.stream_index, output->packets.num);*/
if (stopping(output)) {
uint64_t sys_ts = get_packet_sys_dts(output, &packet);
if (sys_ts >= output->stop_ts) {
ffmpeg_output_full_stop(output);
return 0;
}
}
ret = av_interleaved_write_frame(output->ff_data.output, &packet);
if (ret < 0) {
av_free_packet(&packet);
@@ -955,7 +1000,7 @@ static bool try_connect(struct ffmpeg_output *output)
if (ret != 0) {
blog(LOG_WARNING, "ffmpeg_output_start: failed to create write "
"thread.");
ffmpeg_output_stop(output);
ffmpeg_output_full_stop(output);
return false;
}
@@ -986,11 +1031,15 @@ static bool ffmpeg_output_start(void *data)
if (output->connecting)
return false;
os_atomic_set_bool(&output->stopping, false);
output->audio_start_ts = 0;
output->video_start_ts = 0;
ret = pthread_create(&output->start_thread, NULL, start_thread, output);
return (output->connecting = (ret == 0));
}
static void ffmpeg_output_stop(void *data)
static void ffmpeg_output_full_stop(void *data)
{
struct ffmpeg_output *output = data;
@@ -1000,6 +1049,20 @@ static void ffmpeg_output_stop(void *data)
}
}
static void ffmpeg_output_stop(void *data, uint64_t ts)
{
struct ffmpeg_output *output = data;
if (output->active) {
if (ts == 0) {
ffmpeg_output_full_stop(output);
} else {
os_atomic_set_bool(&output->stopping, true);
output->stop_ts = ts;
}
}
}
static void ffmpeg_deactivate(struct ffmpeg_output *output)
{
if (output->write_thread_active) {

View File

@@ -46,14 +46,14 @@ static const char *flv_output_getname(void *unused)
return obs_module_text("FLVOutput");
}
static void flv_output_stop(void *data);
static void flv_output_stop(void *data, uint64_t ts);
static void flv_output_destroy(void *data)
{
struct flv_output *stream = data;
if (stream->active)
flv_output_stop(data);
flv_output_stop(data, 0);
dstr_free(&stream->path);
bfree(stream);
@@ -68,7 +68,7 @@ static void *flv_output_create(obs_data_t *settings, obs_output_t *output)
return stream;
}
static void flv_output_stop(void *data)
static void flv_output_stop(void *data, uint64_t ts)
{
struct flv_output *stream = data;
@@ -84,6 +84,8 @@ static void flv_output_stop(void *data)
info("FLV file output complete");
}
UNUSED_PARAMETER(ts);
}
static int write_packet(struct flv_output *stream,

View File

@@ -63,6 +63,7 @@ struct rtmp_stream {
os_sem_t *send_sem;
os_event_t *stop_event;
uint64_t stop_ts;
struct dstr path, key;
struct dstr username, password;
@@ -146,6 +147,7 @@ static void rtmp_stream_destroy(void *data)
if (stream->connecting)
pthread_join(stream->connect_thread, NULL);
stream->stop_ts = 0;
os_event_signal(stream->stop_event);
if (active(stream)) {
@@ -193,7 +195,7 @@ fail:
return NULL;
}
static void rtmp_stream_stop(void *data)
static void rtmp_stream_stop(void *data, uint64_t ts)
{
struct rtmp_stream *stream = data;
@@ -203,11 +205,12 @@ static void rtmp_stream_stop(void *data)
if (connecting(stream))
pthread_join(stream->connect_thread, NULL);
stream->stop_ts = ts / 1000ULL;
os_event_signal(stream->stop_event);
if (active(stream)) {
os_sem_post(stream->send_sem);
obs_output_end_data_capture(stream->output);
if (stream->stop_ts == 0)
os_sem_post(stream->send_sem);
}
}
@@ -322,11 +325,20 @@ static void *send_thread(void *data)
while (os_sem_wait(stream->send_sem) == 0) {
struct encoder_packet packet;
if (stopping(stream))
if (stopping(stream) && stream->stop_ts == 0) {
break;
}
if (!get_next_packet(stream, &packet))
continue;
if (stopping(stream)) {
if (packet.sys_dts_usec >= (int64_t)stream->stop_ts) {
obs_free_encoder_packet(&packet);
break;
}
}
if (!stream->sent_headers) {
if (!send_headers(stream)) {
os_atomic_set_bool(&stream->disconnected, true);
@@ -351,6 +363,8 @@ static void *send_thread(void *data)
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);
@@ -795,7 +809,7 @@ static void rtmp_stream_data(void *data, struct encoder_packet *packet)
struct encoder_packet new_packet;
bool added_packet = false;
if (disconnected(stream))
if (disconnected(stream) || !active(stream))
return;
if (packet->type == OBS_ENCODER_VIDEO)