fftools/ffmpeg: convert to a threaded architecture

Change the main loop and every component (demuxers, decoders, filters,
encoders, muxers) to use the previously added transcode scheduler. Every
instance of every such component was already running in a separate
thread, but now they can actually run in parallel.

Changes the results of ffmpeg-fix_sub_duration_heartbeat - tested by
JEEB to be more correct and deterministic.
This commit is contained in:
Anton Khirnov
2023-07-18 16:37:52 +02:00
parent 9b8cc36ce0
commit d119ae2fd8
12 changed files with 609 additions and 2030 deletions

View File

@@ -21,8 +21,6 @@
#include <stdint.h>
#include "ffmpeg.h"
#include "ffmpeg_utils.h"
#include "thread_queue.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
@@ -53,10 +51,11 @@ typedef struct FilterGraphPriv {
// true when the filtergraph contains only meta filters
// that do not modify the frame data
int is_meta;
// source filters are present in the graph
int have_sources;
int disable_conversions;
int nb_inputs_bound;
int nb_outputs_bound;
unsigned nb_outputs_done;
const char *graph_desc;
@@ -67,41 +66,6 @@ typedef struct FilterGraphPriv {
Scheduler *sch;
unsigned sch_idx;
pthread_t thread;
/**
* Queue for sending frames from the main thread to the filtergraph. Has
* nb_inputs+1 streams - the first nb_inputs stream correspond to
* filtergraph inputs. Frames on those streams may have their opaque set to
* - FRAME_OPAQUE_EOF: frame contains no data, but pts+timebase of the
* EOF event for the correspondint stream. Will be immediately followed by
* this stream being send-closed.
* - FRAME_OPAQUE_SUB_HEARTBEAT: frame contains no data, but pts+timebase of
* a subtitle heartbeat event. Will only be sent for sub2video streams.
*
* The last stream is "control" - the main thread sends empty AVFrames with
* opaque set to
* - FRAME_OPAQUE_REAP_FILTERS: a request to retrieve all frame available
* from filtergraph outputs. These frames are sent to corresponding
* streams in queue_out. Finally an empty frame is sent to the control
* stream in queue_out.
* - FRAME_OPAQUE_CHOOSE_INPUT: same as above, but in case no frames are
* available the terminating empty frame's opaque will contain the index+1
* of the filtergraph input to which more input frames should be supplied.
*/
ThreadQueue *queue_in;
/**
* Queue for sending frames from the filtergraph back to the main thread.
* Has nb_outputs+1 streams - the first nb_outputs stream correspond to
* filtergraph outputs.
*
* The last stream is "control" - see documentation for queue_in for more
* details.
*/
ThreadQueue *queue_out;
// submitting frames to filter thread returned EOF
// this only happens on thread exit, so is not per-input
int eof_in;
} FilterGraphPriv;
static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
@@ -123,6 +87,9 @@ typedef struct FilterGraphThread {
// The output index is stored in frame opaque.
AVFifo *frame_queue_out;
// index of the next input to request from the scheduler
unsigned next_in;
// set to 1 after at least one frame passed through this output
int got_frame;
// EOF status of each input/output, as received by the thread
@@ -253,9 +220,6 @@ typedef struct OutputFilterPriv {
int64_t ts_offset;
int64_t next_pts;
FPSConvContext fps;
// set to 1 after at least one frame passed through this output
int got_frame;
} OutputFilterPriv;
static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
@@ -653,57 +617,6 @@ static int ifilter_has_all_input_formats(FilterGraph *fg)
static void *filter_thread(void *arg);
// start the filtering thread once all inputs and outputs are bound
static int fg_thread_try_start(FilterGraphPriv *fgp)
{
FilterGraph *fg = &fgp->fg;
ObjPool *op;
int ret = 0;
if (fgp->nb_inputs_bound < fg->nb_inputs ||
fgp->nb_outputs_bound < fg->nb_outputs)
return 0;
op = objpool_alloc_frames();
if (!op)
return AVERROR(ENOMEM);
fgp->queue_in = tq_alloc(fg->nb_inputs + 1, 1, op, frame_move);
if (!fgp->queue_in) {
objpool_free(&op);
return AVERROR(ENOMEM);
}
// at least one output is mandatory
op = objpool_alloc_frames();
if (!op)
goto fail;
fgp->queue_out = tq_alloc(fg->nb_outputs + 1, 1, op, frame_move);
if (!fgp->queue_out) {
objpool_free(&op);
goto fail;
}
ret = pthread_create(&fgp->thread, NULL, filter_thread, fgp);
if (ret) {
ret = AVERROR(ret);
av_log(NULL, AV_LOG_ERROR, "pthread_create() for filtergraph %d failed: %s\n",
fg->index, av_err2str(ret));
goto fail;
}
return 0;
fail:
if (ret >= 0)
ret = AVERROR(ENOMEM);
tq_free(&fgp->queue_in);
tq_free(&fgp->queue_out);
return ret;
}
static char *describe_filter_link(FilterGraph *fg, AVFilterInOut *inout, int in)
{
AVFilterContext *ctx = inout->filter_ctx;
@@ -729,7 +642,6 @@ static OutputFilter *ofilter_alloc(FilterGraph *fg)
ofilter->graph = fg;
ofp->format = -1;
ofp->index = fg->nb_outputs - 1;
ofilter->last_pts = AV_NOPTS_VALUE;
return ofilter;
}
@@ -760,10 +672,7 @@ static int ifilter_bind_ist(InputFilter *ifilter, InputStream *ist)
return AVERROR(ENOMEM);
}
fgp->nb_inputs_bound++;
av_assert0(fgp->nb_inputs_bound <= ifilter->graph->nb_inputs);
return fg_thread_try_start(fgp);
return 0;
}
static int set_channel_layout(OutputFilterPriv *f, OutputStream *ost)
@@ -902,10 +811,7 @@ int ofilter_bind_ost(OutputFilter *ofilter, OutputStream *ost,
if (ret < 0)
return ret;
fgp->nb_outputs_bound++;
av_assert0(fgp->nb_outputs_bound <= fg->nb_outputs);
return fg_thread_try_start(fgp);
return 0;
}
static InputFilter *ifilter_alloc(FilterGraph *fg)
@@ -935,34 +841,6 @@ static InputFilter *ifilter_alloc(FilterGraph *fg)
return ifilter;
}
static int fg_thread_stop(FilterGraphPriv *fgp)
{
void *ret;
if (!fgp->queue_in)
return 0;
for (int i = 0; i <= fgp->fg.nb_inputs; i++) {
InputFilterPriv *ifp = i < fgp->fg.nb_inputs ?
ifp_from_ifilter(fgp->fg.inputs[i]) : NULL;
if (ifp)
ifp->eof = 1;
tq_send_finish(fgp->queue_in, i);
}
for (int i = 0; i <= fgp->fg.nb_outputs; i++)
tq_receive_finish(fgp->queue_out, i);
pthread_join(fgp->thread, &ret);
tq_free(&fgp->queue_in);
tq_free(&fgp->queue_out);
return (int)(intptr_t)ret;
}
void fg_free(FilterGraph **pfg)
{
FilterGraph *fg = *pfg;
@@ -972,8 +850,6 @@ void fg_free(FilterGraph **pfg)
return;
fgp = fgp_from_fg(fg);
fg_thread_stop(fgp);
avfilter_graph_free(&fg->graph);
for (int j = 0; j < fg->nb_inputs; j++) {
InputFilter *ifilter = fg->inputs[j];
@@ -1072,6 +948,15 @@ int fg_create(FilterGraph **pfg, char *graph_desc, Scheduler *sch)
if (ret < 0)
goto fail;
for (unsigned i = 0; i < graph->nb_filters; i++) {
const AVFilter *f = graph->filters[i]->filter;
if (!avfilter_filter_pad_count(f, 0) &&
!(f->flags & AVFILTER_FLAG_DYNAMIC_INPUTS)) {
fgp->have_sources = 1;
break;
}
}
for (AVFilterInOut *cur = inputs; cur; cur = cur->next) {
InputFilter *const ifilter = ifilter_alloc(fg);
InputFilterPriv *ifp;
@@ -1800,6 +1685,7 @@ static int configure_filtergraph(FilterGraph *fg, const FilterGraphThread *fgt)
AVBufferRef *hw_device;
AVFilterInOut *inputs, *outputs, *cur;
int ret, i, simple = filtergraph_is_simple(fg);
int have_input_eof = 0;
const char *graph_desc = fgp->graph_desc;
cleanup_filtergraph(fg);
@@ -1922,11 +1808,18 @@ static int configure_filtergraph(FilterGraph *fg, const FilterGraphThread *fgt)
ret = av_buffersrc_add_frame(ifp->filter, NULL);
if (ret < 0)
goto fail;
have_input_eof = 1;
}
}
return 0;
if (have_input_eof) {
// make sure the EOF propagates to the end of the graph
ret = avfilter_graph_request_oldest(fg->graph);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
goto fail;
}
return 0;
fail:
cleanup_filtergraph(fg);
return ret;
@@ -2182,7 +2075,7 @@ static void video_sync_process(OutputFilterPriv *ofp, AVFrame *frame,
fps->frames_prev_hist[2]);
if (!*nb_frames && fps->last_dropped) {
ofilter->nb_frames_drop++;
atomic_fetch_add(&ofilter->nb_frames_drop, 1);
fps->last_dropped++;
}
@@ -2260,21 +2153,23 @@ finish:
fps->frames_prev_hist[0] = *nb_frames_prev;
if (*nb_frames_prev == 0 && fps->last_dropped) {
ofilter->nb_frames_drop++;
atomic_fetch_add(&ofilter->nb_frames_drop, 1);
av_log(ost, AV_LOG_VERBOSE,
"*** dropping frame %"PRId64" at ts %"PRId64"\n",
fps->frame_number, fps->last_frame->pts);
}
if (*nb_frames > (*nb_frames_prev && fps->last_dropped) + (*nb_frames > *nb_frames_prev)) {
uint64_t nb_frames_dup;
if (*nb_frames > dts_error_threshold * 30) {
av_log(ost, AV_LOG_ERROR, "%"PRId64" frame duplication too large, skipping\n", *nb_frames - 1);
ofilter->nb_frames_drop++;
atomic_fetch_add(&ofilter->nb_frames_drop, 1);
*nb_frames = 0;
return;
}
ofilter->nb_frames_dup += *nb_frames - (*nb_frames_prev && fps->last_dropped) - (*nb_frames > *nb_frames_prev);
nb_frames_dup = atomic_fetch_add(&ofilter->nb_frames_dup,
*nb_frames - (*nb_frames_prev && fps->last_dropped) - (*nb_frames > *nb_frames_prev));
av_log(ost, AV_LOG_VERBOSE, "*** %"PRId64" dup!\n", *nb_frames - 1);
if (ofilter->nb_frames_dup > fps->dup_warning) {
if (nb_frames_dup > fps->dup_warning) {
av_log(ost, AV_LOG_WARNING, "More than %"PRIu64" frames duplicated\n", fps->dup_warning);
fps->dup_warning *= 10;
}
@@ -2284,8 +2179,57 @@ finish:
fps->dropped_keyframe |= fps->last_dropped && (frame->flags & AV_FRAME_FLAG_KEY);
}
static int close_output(OutputFilterPriv *ofp, FilterGraphThread *fgt)
{
FilterGraphPriv *fgp = fgp_from_fg(ofp->ofilter.graph);
int ret;
// we are finished and no frames were ever seen at this output,
// at least initialize the encoder with a dummy frame
if (!fgt->got_frame) {
AVFrame *frame = fgt->frame;
FrameData *fd;
frame->time_base = ofp->tb_out;
frame->format = ofp->format;
frame->width = ofp->width;
frame->height = ofp->height;
frame->sample_aspect_ratio = ofp->sample_aspect_ratio;
frame->sample_rate = ofp->sample_rate;
if (ofp->ch_layout.nb_channels) {
ret = av_channel_layout_copy(&frame->ch_layout, &ofp->ch_layout);
if (ret < 0)
return ret;
}
fd = frame_data(frame);
if (!fd)
return AVERROR(ENOMEM);
fd->frame_rate_filter = ofp->fps.framerate;
av_assert0(!frame->buf[0]);
av_log(ofp->ofilter.ost, AV_LOG_WARNING,
"No filtered frames for output stream, trying to "
"initialize anyway.\n");
ret = sch_filter_send(fgp->sch, fgp->sch_idx, ofp->index, frame);
if (ret < 0) {
av_frame_unref(frame);
return ret;
}
}
fgt->eof_out[ofp->index] = 1;
return sch_filter_send(fgp->sch, fgp->sch_idx, ofp->index, NULL);
}
static int fg_output_frame(OutputFilterPriv *ofp, FilterGraphThread *fgt,
AVFrame *frame, int buffer)
AVFrame *frame)
{
FilterGraphPriv *fgp = fgp_from_fg(ofp->ofilter.graph);
AVFrame *frame_prev = ofp->fps.last_frame;
@@ -2332,28 +2276,17 @@ static int fg_output_frame(OutputFilterPriv *ofp, FilterGraphThread *fgt,
frame_out = frame;
}
if (buffer) {
AVFrame *f = av_frame_alloc();
if (!f) {
av_frame_unref(frame_out);
return AVERROR(ENOMEM);
}
av_frame_move_ref(f, frame_out);
f->opaque = (void*)(intptr_t)ofp->index;
ret = av_fifo_write(fgt->frame_queue_out, &f, 1);
if (ret < 0) {
av_frame_free(&f);
return AVERROR(ENOMEM);
}
} else {
// return the frame to the main thread
ret = tq_send(fgp->queue_out, ofp->index, frame_out);
{
// send the frame to consumers
ret = sch_filter_send(fgp->sch, fgp->sch_idx, ofp->index, frame_out);
if (ret < 0) {
av_frame_unref(frame_out);
fgt->eof_out[ofp->index] = 1;
if (!fgt->eof_out[ofp->index]) {
fgt->eof_out[ofp->index] = 1;
fgp->nb_outputs_done++;
}
return ret == AVERROR_EOF ? 0 : ret;
}
}
@@ -2374,16 +2307,14 @@ static int fg_output_frame(OutputFilterPriv *ofp, FilterGraphThread *fgt,
av_frame_move_ref(frame_prev, frame);
}
if (!frame) {
tq_send_finish(fgp->queue_out, ofp->index);
fgt->eof_out[ofp->index] = 1;
}
if (!frame)
return close_output(ofp, fgt);
return 0;
}
static int fg_output_step(OutputFilterPriv *ofp, FilterGraphThread *fgt,
AVFrame *frame, int buffer)
AVFrame *frame)
{
FilterGraphPriv *fgp = fgp_from_fg(ofp->ofilter.graph);
OutputStream *ost = ofp->ofilter.ost;
@@ -2393,8 +2324,8 @@ static int fg_output_step(OutputFilterPriv *ofp, FilterGraphThread *fgt,
ret = av_buffersink_get_frame_flags(filter, frame,
AV_BUFFERSINK_FLAG_NO_REQUEST);
if (ret == AVERROR_EOF && !buffer && !fgt->eof_out[ofp->index]) {
ret = fg_output_frame(ofp, fgt, NULL, buffer);
if (ret == AVERROR_EOF && !fgt->eof_out[ofp->index]) {
ret = fg_output_frame(ofp, fgt, NULL);
return (ret < 0) ? ret : 1;
} else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 1;
@@ -2448,7 +2379,7 @@ static int fg_output_step(OutputFilterPriv *ofp, FilterGraphThread *fgt,
fd->frame_rate_filter = ofp->fps.framerate;
}
ret = fg_output_frame(ofp, fgt, frame, buffer);
ret = fg_output_frame(ofp, fgt, frame);
av_frame_unref(frame);
if (ret < 0)
return ret;
@@ -2456,44 +2387,68 @@ static int fg_output_step(OutputFilterPriv *ofp, FilterGraphThread *fgt,
return 0;
}
/* retrieve all frames available at filtergraph outputs and either send them to
* the main thread (buffer=0) or buffer them for later (buffer=1) */
/* retrieve all frames available at filtergraph outputs
* and send them to consumers */
static int read_frames(FilterGraph *fg, FilterGraphThread *fgt,
AVFrame *frame, int buffer)
AVFrame *frame)
{
FilterGraphPriv *fgp = fgp_from_fg(fg);
int ret = 0;
int did_step = 0;
if (!fg->graph)
return 0;
// process buffered frames
if (!buffer) {
AVFrame *f;
while (av_fifo_read(fgt->frame_queue_out, &f, 1) >= 0) {
int out_idx = (intptr_t)f->opaque;
f->opaque = NULL;
ret = tq_send(fgp->queue_out, out_idx, f);
av_frame_free(&f);
if (ret < 0 && ret != AVERROR_EOF)
return ret;
// graph not configured, just select the input to request
if (!fg->graph) {
for (int i = 0; i < fg->nb_inputs; i++) {
InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[i]);
if (ifp->format < 0 && !fgt->eof_in[i]) {
fgt->next_in = i;
return 0;
}
}
// This state - graph is not configured, but all inputs are either
// initialized or EOF - should be unreachable because sending EOF to a
// filter without even a fallback format should fail
av_assert0(0);
return AVERROR_BUG;
}
/* Reap all buffers present in the buffer sinks */
for (int i = 0; i < fg->nb_outputs; i++) {
OutputFilterPriv *ofp = ofp_from_ofilter(fg->outputs[i]);
int ret = 0;
while (fgp->nb_outputs_done < fg->nb_outputs) {
int ret;
while (!ret) {
ret = fg_output_step(ofp, fgt, frame, buffer);
if (ret < 0)
return ret;
ret = avfilter_graph_request_oldest(fg->graph);
if (ret == AVERROR(EAGAIN)) {
fgt->next_in = choose_input(fg, fgt);
break;
} else if (ret < 0) {
if (ret == AVERROR_EOF)
av_log(fg, AV_LOG_VERBOSE, "Filtergraph returned EOF, finishing\n");
else
av_log(fg, AV_LOG_ERROR,
"Error requesting a frame from the filtergraph: %s\n",
av_err2str(ret));
return ret;
}
}
fgt->next_in = fg->nb_inputs;
return 0;
// return after one iteration, so that scheduler can rate-control us
if (did_step && fgp->have_sources)
return 0;
/* Reap all buffers present in the buffer sinks */
for (int i = 0; i < fg->nb_outputs; i++) {
OutputFilterPriv *ofp = ofp_from_ofilter(fg->outputs[i]);
ret = 0;
while (!ret) {
ret = fg_output_step(ofp, fgt, frame);
if (ret < 0)
return ret;
}
}
did_step = 1;
};
return (fgp->nb_outputs_done == fg->nb_outputs) ? AVERROR_EOF : 0;
}
static void sub2video_heartbeat(InputFilter *ifilter, int64_t pts, AVRational tb)
@@ -2571,6 +2526,9 @@ static int send_eof(FilterGraphThread *fgt, InputFilter *ifilter,
InputFilterPriv *ifp = ifp_from_ifilter(ifilter);
int ret;
if (fgt->eof_in[ifp->index])
return 0;
fgt->eof_in[ifp->index] = 1;
if (ifp->filter) {
@@ -2672,7 +2630,7 @@ static int send_frame(FilterGraph *fg, FilterGraphThread *fgt,
return ret;
}
ret = fg->graph ? read_frames(fg, fgt, tmp, 1) : 0;
ret = fg->graph ? read_frames(fg, fgt, tmp) : 0;
av_frame_free(&tmp);
if (ret < 0)
return ret;
@@ -2705,82 +2663,6 @@ static int send_frame(FilterGraph *fg, FilterGraphThread *fgt,
return 0;
}
static int msg_process(FilterGraphPriv *fgp, FilterGraphThread *fgt,
AVFrame *frame)
{
const enum FrameOpaque msg = (intptr_t)frame->opaque;
FilterGraph *fg = &fgp->fg;
int graph_eof = 0;
int ret;
frame->opaque = NULL;
av_assert0(msg > 0);
av_assert0(msg == FRAME_OPAQUE_SEND_COMMAND || !frame->buf[0]);
if (!fg->graph) {
// graph not configured yet, ignore all messages other than choosing
// the input to read from
if (msg != FRAME_OPAQUE_CHOOSE_INPUT) {
av_frame_unref(frame);
goto done;
}
for (int i = 0; i < fg->nb_inputs; i++) {
InputFilter *ifilter = fg->inputs[i];
InputFilterPriv *ifp = ifp_from_ifilter(ifilter);
if (ifp->format < 0 && !fgt->eof_in[i]) {
frame->opaque = (void*)(intptr_t)(i + 1);
goto done;
}
}
// This state - graph is not configured, but all inputs are either
// initialized or EOF - should be unreachable because sending EOF to a
// filter without even a fallback format should fail
av_assert0(0);
return AVERROR_BUG;
}
if (msg == FRAME_OPAQUE_SEND_COMMAND) {
FilterCommand *fc = (FilterCommand*)frame->buf[0]->data;
send_command(fg, fc->time, fc->target, fc->command, fc->arg, fc->all_filters);
av_frame_unref(frame);
goto done;
}
if (msg == FRAME_OPAQUE_CHOOSE_INPUT) {
ret = avfilter_graph_request_oldest(fg->graph);
graph_eof = ret == AVERROR_EOF;
if (ret == AVERROR(EAGAIN)) {
frame->opaque = (void*)(intptr_t)(choose_input(fg, fgt) + 1);
goto done;
} else if (ret < 0 && !graph_eof)
return ret;
}
ret = read_frames(fg, fgt, frame, 0);
if (ret < 0) {
av_log(fg, AV_LOG_ERROR, "Error sending filtered frames for encoding\n");
return ret;
}
if (graph_eof)
return AVERROR_EOF;
// signal to the main thread that we are done processing the message
done:
ret = tq_send(fgp->queue_out, fg->nb_outputs, frame);
if (ret < 0) {
if (ret != AVERROR_EOF)
av_log(fg, AV_LOG_ERROR, "Error communicating with the main thread\n");
return ret;
}
return 0;
}
static void fg_thread_set_name(const FilterGraph *fg)
{
char name[16];
@@ -2867,294 +2749,94 @@ static void *filter_thread(void *arg)
InputFilter *ifilter;
InputFilterPriv *ifp;
enum FrameOpaque o;
int input_idx, eof_frame;
unsigned input_idx = fgt.next_in;
input_status = tq_receive(fgp->queue_in, &input_idx, fgt.frame);
if (input_idx < 0 ||
(input_idx == fg->nb_inputs && input_status < 0)) {
input_status = sch_filter_receive(fgp->sch, fgp->sch_idx,
&input_idx, fgt.frame);
if (input_status == AVERROR_EOF) {
av_log(fg, AV_LOG_VERBOSE, "Filtering thread received EOF\n");
break;
} else if (input_status == AVERROR(EAGAIN)) {
// should only happen when we didn't request any input
av_assert0(input_idx == fg->nb_inputs);
goto read_frames;
}
av_assert0(input_status >= 0);
o = (intptr_t)fgt.frame->opaque;
o = (intptr_t)fgt.frame->opaque;
// message on the control stream
if (input_idx == fg->nb_inputs) {
ret = msg_process(fgp, &fgt, fgt.frame);
if (ret < 0)
goto finish;
FilterCommand *fc;
av_assert0(o == FRAME_OPAQUE_SEND_COMMAND && fgt.frame->buf[0]);
fc = (FilterCommand*)fgt.frame->buf[0]->data;
send_command(fg, fc->time, fc->target, fc->command, fc->arg,
fc->all_filters);
av_frame_unref(fgt.frame);
continue;
}
// we received an input frame or EOF
ifilter = fg->inputs[input_idx];
ifp = ifp_from_ifilter(ifilter);
eof_frame = input_status >= 0 && o == FRAME_OPAQUE_EOF;
if (ifp->type_src == AVMEDIA_TYPE_SUBTITLE) {
int hb_frame = input_status >= 0 && o == FRAME_OPAQUE_SUB_HEARTBEAT;
ret = sub2video_frame(ifilter, (fgt.frame->buf[0] || hb_frame) ? fgt.frame : NULL);
} else if (input_status >= 0 && fgt.frame->buf[0]) {
} else if (fgt.frame->buf[0]) {
ret = send_frame(fg, &fgt, ifilter, fgt.frame);
} else {
int64_t pts = input_status >= 0 ? fgt.frame->pts : AV_NOPTS_VALUE;
AVRational tb = input_status >= 0 ? fgt.frame->time_base : (AVRational){ 1, 1 };
ret = send_eof(&fgt, ifilter, pts, tb);
av_assert1(o == FRAME_OPAQUE_EOF);
ret = send_eof(&fgt, ifilter, fgt.frame->pts, fgt.frame->time_base);
}
av_frame_unref(fgt.frame);
if (ret < 0)
goto finish;
read_frames:
// retrieve all newly avalable frames
ret = read_frames(fg, &fgt, fgt.frame);
if (ret == AVERROR_EOF) {
av_log(fg, AV_LOG_VERBOSE, "All consumers returned EOF\n");
break;
if (eof_frame) {
// an EOF frame is immediately followed by sender closing
// the corresponding stream, so retrieve that event
input_status = tq_receive(fgp->queue_in, &input_idx, fgt.frame);
av_assert0(input_status == AVERROR_EOF && input_idx == ifp->index);
}
// signal to the main thread that we are done
ret = tq_send(fgp->queue_out, fg->nb_outputs, fgt.frame);
if (ret < 0) {
if (ret == AVERROR_EOF)
break;
av_log(fg, AV_LOG_ERROR, "Error communicating with the main thread\n");
} else if (ret < 0) {
av_log(fg, AV_LOG_ERROR, "Error sending frames to consumers: %s\n",
av_err2str(ret));
goto finish;
}
}
for (unsigned i = 0; i < fg->nb_outputs; i++) {
OutputFilterPriv *ofp = ofp_from_ofilter(fg->outputs[i]);
if (fgt.eof_out[i])
continue;
ret = fg_output_frame(ofp, &fgt, NULL);
if (ret < 0)
goto finish;
}
finish:
// EOF is normal termination
if (ret == AVERROR_EOF)
ret = 0;
for (int i = 0; i <= fg->nb_inputs; i++)
tq_receive_finish(fgp->queue_in, i);
for (int i = 0; i <= fg->nb_outputs; i++)
tq_send_finish(fgp->queue_out, i);
fg_thread_uninit(&fgt);
av_log(fg, AV_LOG_VERBOSE, "Terminating filtering thread\n");
return (void*)(intptr_t)ret;
}
static int thread_send_frame(FilterGraphPriv *fgp, InputFilter *ifilter,
AVFrame *frame, enum FrameOpaque type)
{
InputFilterPriv *ifp = ifp_from_ifilter(ifilter);
int output_idx, ret;
if (ifp->eof) {
av_frame_unref(frame);
return AVERROR_EOF;
}
frame->opaque = (void*)(intptr_t)type;
ret = tq_send(fgp->queue_in, ifp->index, frame);
if (ret < 0) {
ifp->eof = 1;
av_frame_unref(frame);
return ret;
}
if (type == FRAME_OPAQUE_EOF)
tq_send_finish(fgp->queue_in, ifp->index);
// wait for the frame to be processed
ret = tq_receive(fgp->queue_out, &output_idx, frame);
av_assert0(output_idx == fgp->fg.nb_outputs || ret == AVERROR_EOF);
return ret;
}
int ifilter_send_frame(InputFilter *ifilter, AVFrame *frame, int keep_reference)
{
FilterGraphPriv *fgp = fgp_from_fg(ifilter->graph);
int ret;
if (keep_reference) {
ret = av_frame_ref(fgp->frame, frame);
if (ret < 0)
return ret;
} else
av_frame_move_ref(fgp->frame, frame);
return thread_send_frame(fgp, ifilter, fgp->frame, 0);
}
int ifilter_send_eof(InputFilter *ifilter, int64_t pts, AVRational tb)
{
FilterGraphPriv *fgp = fgp_from_fg(ifilter->graph);
int ret;
fgp->frame->pts = pts;
fgp->frame->time_base = tb;
ret = thread_send_frame(fgp, ifilter, fgp->frame, FRAME_OPAQUE_EOF);
return ret == AVERROR_EOF ? 0 : ret;
}
void ifilter_sub2video_heartbeat(InputFilter *ifilter, int64_t pts, AVRational tb)
{
FilterGraphPriv *fgp = fgp_from_fg(ifilter->graph);
fgp->frame->pts = pts;
fgp->frame->time_base = tb;
thread_send_frame(fgp, ifilter, fgp->frame, FRAME_OPAQUE_SUB_HEARTBEAT);
}
int fg_transcode_step(FilterGraph *graph, InputStream **best_ist)
{
FilterGraphPriv *fgp = fgp_from_fg(graph);
int ret, got_frames = 0;
if (fgp->eof_in)
return AVERROR_EOF;
// signal to the filtering thread to return all frames it can
av_assert0(!fgp->frame->buf[0]);
fgp->frame->opaque = (void*)(intptr_t)(best_ist ?
FRAME_OPAQUE_CHOOSE_INPUT :
FRAME_OPAQUE_REAP_FILTERS);
ret = tq_send(fgp->queue_in, graph->nb_inputs, fgp->frame);
if (ret < 0) {
fgp->eof_in = 1;
goto finish;
}
while (1) {
OutputFilter *ofilter;
OutputFilterPriv *ofp;
OutputStream *ost;
int output_idx;
ret = tq_receive(fgp->queue_out, &output_idx, fgp->frame);
// EOF on the whole queue or the control stream
if (output_idx < 0 ||
(ret < 0 && output_idx == graph->nb_outputs))
goto finish;
// EOF for a specific stream
if (ret < 0) {
ofilter = graph->outputs[output_idx];
ofp = ofp_from_ofilter(ofilter);
// we are finished and no frames were ever seen at this output,
// at least initialize the encoder with a dummy frame
if (!ofp->got_frame) {
AVFrame *frame = fgp->frame;
FrameData *fd;
frame->time_base = ofp->tb_out;
frame->format = ofp->format;
frame->width = ofp->width;
frame->height = ofp->height;
frame->sample_aspect_ratio = ofp->sample_aspect_ratio;
frame->sample_rate = ofp->sample_rate;
if (ofp->ch_layout.nb_channels) {
ret = av_channel_layout_copy(&frame->ch_layout, &ofp->ch_layout);
if (ret < 0)
return ret;
}
fd = frame_data(frame);
if (!fd)
return AVERROR(ENOMEM);
fd->frame_rate_filter = ofp->fps.framerate;
av_assert0(!frame->buf[0]);
av_log(ofilter->ost, AV_LOG_WARNING,
"No filtered frames for output stream, trying to "
"initialize anyway.\n");
enc_open(ofilter->ost, frame);
av_frame_unref(frame);
}
close_output_stream(graph->outputs[output_idx]->ost);
continue;
}
// request was fully processed by the filtering thread,
// return the input stream to read from, if needed
if (output_idx == graph->nb_outputs) {
int input_idx = (intptr_t)fgp->frame->opaque - 1;
av_assert0(input_idx <= graph->nb_inputs);
if (best_ist) {
*best_ist = (input_idx >= 0 && input_idx < graph->nb_inputs) ?
ifp_from_ifilter(graph->inputs[input_idx])->ist : NULL;
if (input_idx < 0 && !got_frames) {
for (int i = 0; i < graph->nb_outputs; i++)
graph->outputs[i]->ost->unavailable = 1;
}
}
break;
}
// got a frame from the filtering thread, send it for encoding
ofilter = graph->outputs[output_idx];
ost = ofilter->ost;
ofp = ofp_from_ofilter(ofilter);
if (ost->finished) {
av_frame_unref(fgp->frame);
tq_receive_finish(fgp->queue_out, output_idx);
continue;
}
if (fgp->frame->pts != AV_NOPTS_VALUE) {
ofilter->last_pts = av_rescale_q(fgp->frame->pts,
fgp->frame->time_base,
AV_TIME_BASE_Q);
}
ret = enc_frame(ost, fgp->frame);
av_frame_unref(fgp->frame);
if (ret < 0)
goto finish;
ofp->got_frame = 1;
got_frames = 1;
}
finish:
if (ret < 0) {
fgp->eof_in = 1;
for (int i = 0; i < graph->nb_outputs; i++)
close_output_stream(graph->outputs[i]->ost);
}
return ret;
}
int reap_filters(FilterGraph *fg, int flush)
{
return fg_transcode_step(fg, NULL);
}
void fg_send_command(FilterGraph *fg, double time, const char *target,
const char *command, const char *arg, int all_filters)
{
FilterGraphPriv *fgp = fgp_from_fg(fg);
AVBufferRef *buf;
FilterCommand *fc;
int output_idx, ret;
if (!fgp->queue_in)
return;
fc = av_mallocz(sizeof(*fc));
if (!fc)
@@ -3180,13 +2862,5 @@ void fg_send_command(FilterGraph *fg, double time, const char *target,
fgp->frame->buf[0] = buf;
fgp->frame->opaque = (void*)(intptr_t)FRAME_OPAQUE_SEND_COMMAND;
ret = tq_send(fgp->queue_in, fg->nb_inputs, fgp->frame);
if (ret < 0) {
av_frame_unref(fgp->frame);
return;
}
// wait for the frame to be processed
ret = tq_receive(fgp->queue_out, &output_idx, fgp->frame);
av_assert0(output_idx == fgp->fg.nb_outputs || ret == AVERROR_EOF);
sch_filter_command(fgp->sch, fgp->sch_idx, fgp->frame);
}