3 * Copyright (c) 2016 Jan Sebechlebsky
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public License
9 * as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with FFmpeg; if not, write to the Free Software * Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 #include <stdatomic.h>
24 #include "libavutil/avassert.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/time.h"
27 #include "libavutil/thread.h"
28 #include "libavutil/threadmessage.h"
33 #define FIFO_DEFAULT_QUEUE_SIZE 60
34 #define FIFO_DEFAULT_MAX_RECOVERY_ATTEMPTS 0
35 #define FIFO_DEFAULT_RECOVERY_WAIT_TIME_USEC 5000000 // 5 seconds
37 typedef struct FifoContext
{
42 AVDictionary
*format_options
;
45 AVThreadMessageQueue
*queue
;
47 pthread_t writer_thread
;
49 /* Return value of last write_trailer_call */
50 int write_trailer_ret
;
52 /* Time to wait before next recovery attempt
53 * This can refer to the time in processed stream,
55 int64_t recovery_wait_time
;
57 /* Maximal number of unsuccessful successive recovery attempts */
58 int max_recovery_attempts
;
60 /* Whether to attempt recovery from failure */
63 /* If >0 stream time will be used when waiting
64 * for the recovery attempt instead of real time */
65 int recovery_wait_streamtime
;
67 /* If >0 recovery will be attempted regardless of error code
68 * (except AVERROR_EXIT, so exit request is never ignored) */
69 int recover_any_error
;
71 /* Whether to drop packets in case the queue is full. */
72 int drop_pkts_on_overflow
;
74 /* Whether to wait for keyframe when recovering
75 * from failure or queue overflow */
76 int restart_with_keyframe
;
78 pthread_mutex_t overflow_flag_lock
;
79 int overflow_flag_lock_initialized
;
80 /* Value > 0 signals queue overflow */
81 volatile uint8_t overflow_flag
;
83 atomic_int_least64_t queue_duration
;
84 int64_t last_sent_dts
;
88 typedef struct FifoThreadContext
{
91 /* Timestamp of last failure.
92 * This is either pts in case stream time is used,
93 * or microseconds as returned by av_gettime_relative() */
94 int64_t last_recovery_ts
;
96 /* Number of current recovery process
97 * Value > 0 means we are in recovery process */
100 /* If > 0 all frames will be dropped until keyframe is received */
101 uint8_t drop_until_keyframe
;
103 /* Value > 0 means that the previous write_header call was successful
104 * so finalization by calling write_trailer and ff_io_close must be done
105 * before exiting / reinitialization of underlying muxer */
106 uint8_t header_written
;
108 int64_t last_received_dts
;
110 /* If > 0 at least one of the streams is a video stream */
111 uint8_t has_video_stream
;
114 typedef enum FifoMessageType
{
121 typedef struct FifoMessage
{
122 FifoMessageType type
;
126 static int fifo_thread_write_header(FifoThreadContext
*ctx
)
128 AVFormatContext
*avf
= ctx
->avf
;
129 FifoContext
*fifo
= avf
->priv_data
;
130 AVFormatContext
*avf2
= fifo
->avf
;
131 AVDictionary
*format_options
= NULL
;
134 ret
= av_dict_copy(&format_options
, fifo
->format_options
, 0);
138 ret
= ff_format_output_open(avf2
, avf
->url
, &format_options
);
140 av_log(avf
, AV_LOG_ERROR
, "Error opening %s: %s\n", avf
->url
,
145 for (i
= 0;i
< avf2
->nb_streams
; i
++)
146 ffstream(avf2
->streams
[i
])->cur_dts
= 0;
148 ret
= avformat_write_header(avf2
, &format_options
);
150 ctx
->header_written
= 1;
152 // Check for options unrecognized by underlying muxer
153 if (format_options
) {
154 const AVDictionaryEntry
*entry
= NULL
;
155 while ((entry
= av_dict_iterate(format_options
, entry
)))
156 av_log(avf2
, AV_LOG_ERROR
, "Unknown option '%s'\n", entry
->key
);
157 ret
= AVERROR(EINVAL
);
161 av_dict_free(&format_options
);
165 static int fifo_thread_flush_output(FifoThreadContext
*ctx
)
167 AVFormatContext
*avf
= ctx
->avf
;
168 FifoContext
*fifo
= avf
->priv_data
;
169 AVFormatContext
*avf2
= fifo
->avf
;
171 return av_write_frame(avf2
, NULL
);
174 static int64_t next_duration(AVFormatContext
*avf
, AVPacket
*pkt
, int64_t *last_dts
)
176 AVStream
*st
= avf
->streams
[pkt
->stream_index
];
177 int64_t dts
= av_rescale_q(pkt
->dts
, st
->time_base
, AV_TIME_BASE_Q
);
178 int64_t duration
= (*last_dts
== AV_NOPTS_VALUE
? 0 : dts
- *last_dts
);
183 static int fifo_thread_write_packet(FifoThreadContext
*ctx
, AVPacket
*pkt
)
185 AVFormatContext
*avf
= ctx
->avf
;
186 FifoContext
*fifo
= avf
->priv_data
;
187 AVFormatContext
*avf2
= fifo
->avf
;
188 AVRational src_tb
, dst_tb
;
190 int64_t orig_pts
, orig_dts
, orig_duration
;
191 enum AVMediaType stream_codec_type
= avf
->streams
[pkt
->stream_index
]->codecpar
->codec_type
;
193 if (fifo
->timeshift
&& pkt
->dts
!= AV_NOPTS_VALUE
)
194 atomic_fetch_sub_explicit(&fifo
->queue_duration
, next_duration(avf
, pkt
, &ctx
->last_received_dts
), memory_order_relaxed
);
196 if (ctx
->drop_until_keyframe
) {
197 if (pkt
->flags
& AV_PKT_FLAG_KEY
) {
198 if (!ctx
->has_video_stream
) {
199 ctx
->drop_until_keyframe
= 0;
200 av_log(avf
, AV_LOG_VERBOSE
, "Keyframe received, recovering...\n");
202 if (stream_codec_type
== AVMEDIA_TYPE_VIDEO
) {
203 ctx
->drop_until_keyframe
= 0;
204 av_log(avf
, AV_LOG_VERBOSE
, "Video keyframe received, recovering...\n");
206 av_log(avf
, AV_LOG_VERBOSE
, "Dropping non-video keyframe\n");
207 av_packet_unref(pkt
);
212 av_log(avf
, AV_LOG_VERBOSE
, "Dropping non-keyframe packet\n");
213 av_packet_unref(pkt
);
220 orig_duration
= pkt
->duration
;
221 s_idx
= pkt
->stream_index
;
222 src_tb
= avf
->streams
[s_idx
]->time_base
;
223 dst_tb
= avf2
->streams
[s_idx
]->time_base
;
224 av_packet_rescale_ts(pkt
, src_tb
, dst_tb
);
226 ret
= av_write_frame(avf2
, pkt
);
228 av_packet_unref(pkt
);
230 // avoid scaling twice
233 pkt
->duration
= orig_duration
;
238 static int fifo_thread_write_trailer(FifoThreadContext
*ctx
)
240 AVFormatContext
*avf
= ctx
->avf
;
241 FifoContext
*fifo
= avf
->priv_data
;
242 AVFormatContext
*avf2
= fifo
->avf
;
245 if (!ctx
->header_written
)
248 ret
= av_write_trailer(avf2
);
249 ff_format_io_close(avf2
, &avf2
->pb
);
254 static int fifo_thread_dispatch_message(FifoThreadContext
*ctx
, FifoMessage
*msg
)
256 int ret
= AVERROR(EINVAL
);
258 if (msg
->type
== FIFO_NOOP
)
261 if (!ctx
->header_written
) {
262 ret
= fifo_thread_write_header(ctx
);
268 case FIFO_WRITE_HEADER
:
269 av_assert0(ret
>= 0);
271 case FIFO_WRITE_PACKET
:
272 return fifo_thread_write_packet(ctx
, &msg
->pkt
);
273 case FIFO_FLUSH_OUTPUT
:
274 return fifo_thread_flush_output(ctx
);
278 return AVERROR(EINVAL
);
281 static int is_recoverable(const FifoContext
*fifo
, int err_no
) {
282 if (!fifo
->attempt_recovery
)
285 if (fifo
->recover_any_error
)
286 return err_no
!= AVERROR_EXIT
;
289 case AVERROR(EINVAL
):
290 case AVERROR(ENOSYS
):
293 case AVERROR_PATCHWELCOME
:
300 static void free_message(void *msg
)
302 FifoMessage
*fifo_msg
= msg
;
304 if (fifo_msg
->type
== FIFO_WRITE_PACKET
)
305 av_packet_unref(&fifo_msg
->pkt
);
308 static int fifo_thread_process_recovery_failure(FifoThreadContext
*ctx
, AVPacket
*pkt
,
311 AVFormatContext
*avf
= ctx
->avf
;
312 FifoContext
*fifo
= avf
->priv_data
;
315 av_log(avf
, AV_LOG_INFO
, "Recovery failed: %s\n",
318 if (fifo
->recovery_wait_streamtime
) {
319 if (pkt
->pts
== AV_NOPTS_VALUE
)
320 av_log(avf
, AV_LOG_WARNING
, "Packet does not contain presentation"
321 " timestamp, recovery will be attempted immediately");
322 ctx
->last_recovery_ts
= pkt
->pts
;
324 ctx
->last_recovery_ts
= av_gettime_relative();
327 if (fifo
->max_recovery_attempts
&&
328 ctx
->recovery_nr
>= fifo
->max_recovery_attempts
) {
329 av_log(avf
, AV_LOG_ERROR
,
330 "Maximal number of %d recovery attempts reached.\n",
331 fifo
->max_recovery_attempts
);
334 ret
= AVERROR(EAGAIN
);
340 static int fifo_thread_attempt_recovery(FifoThreadContext
*ctx
, FifoMessage
*msg
, int err_no
)
342 AVFormatContext
*avf
= ctx
->avf
;
343 FifoContext
*fifo
= avf
->priv_data
;
344 AVPacket
*pkt
= &msg
->pkt
;
345 int64_t time_since_recovery
;
348 if (!is_recoverable(fifo
, err_no
)) {
353 if (ctx
->header_written
) {
354 fifo
->write_trailer_ret
= fifo_thread_write_trailer(ctx
);
355 ctx
->header_written
= 0;
358 if (!ctx
->recovery_nr
) {
359 ctx
->last_recovery_ts
= fifo
->recovery_wait_streamtime
?
362 if (fifo
->recovery_wait_streamtime
) {
363 if (ctx
->last_recovery_ts
== AV_NOPTS_VALUE
) {
364 AVRational tb
= avf
->streams
[pkt
->stream_index
]->time_base
;
365 time_since_recovery
= av_rescale_q(pkt
->pts
- ctx
->last_recovery_ts
,
368 /* Enforce recovery immediately */
369 time_since_recovery
= fifo
->recovery_wait_time
;
372 time_since_recovery
= av_gettime_relative() - ctx
->last_recovery_ts
;
375 if (time_since_recovery
< fifo
->recovery_wait_time
)
376 return AVERROR(EAGAIN
);
381 if (fifo
->max_recovery_attempts
) {
382 av_log(avf
, AV_LOG_VERBOSE
, "Recovery attempt #%d/%d\n",
383 ctx
->recovery_nr
, fifo
->max_recovery_attempts
);
385 av_log(avf
, AV_LOG_VERBOSE
, "Recovery attempt #%d\n",
389 if (fifo
->restart_with_keyframe
&& fifo
->drop_pkts_on_overflow
)
390 ctx
->drop_until_keyframe
= 1;
392 ret
= fifo_thread_dispatch_message(ctx
, msg
);
394 if (is_recoverable(fifo
, ret
)) {
395 return fifo_thread_process_recovery_failure(ctx
, pkt
, ret
);
400 av_log(avf
, AV_LOG_INFO
, "Recovery successful\n");
401 ctx
->recovery_nr
= 0;
411 static int fifo_thread_recover(FifoThreadContext
*ctx
, FifoMessage
*msg
, int err_no
)
413 AVFormatContext
*avf
= ctx
->avf
;
414 FifoContext
*fifo
= avf
->priv_data
;
418 if (!fifo
->recovery_wait_streamtime
&& ctx
->recovery_nr
> 0) {
419 int64_t time_since_recovery
= av_gettime_relative() - ctx
->last_recovery_ts
;
420 int64_t time_to_wait
= FFMAX(0, fifo
->recovery_wait_time
- time_since_recovery
);
422 av_usleep(FFMIN(10000, time_to_wait
));
425 ret
= fifo_thread_attempt_recovery(ctx
, msg
, err_no
);
426 } while (ret
== AVERROR(EAGAIN
) && !fifo
->drop_pkts_on_overflow
);
428 if (ret
== AVERROR(EAGAIN
) && fifo
->drop_pkts_on_overflow
) {
429 if (msg
->type
== FIFO_WRITE_PACKET
)
430 av_packet_unref(&msg
->pkt
);
437 static void *fifo_consumer_thread(void *data
)
439 AVFormatContext
*avf
= data
;
440 FifoContext
*fifo
= avf
->priv_data
;
441 AVThreadMessageQueue
*queue
= fifo
->queue
;
442 FifoMessage msg
= {fifo
->timeshift
? FIFO_NOOP
: FIFO_WRITE_HEADER
, {0}};
445 FifoThreadContext fifo_thread_ctx
;
446 memset(&fifo_thread_ctx
, 0, sizeof(FifoThreadContext
));
447 fifo_thread_ctx
.avf
= avf
;
448 fifo_thread_ctx
.last_received_dts
= AV_NOPTS_VALUE
;
450 ff_thread_setname("fifo-consumer");
452 for (i
= 0; i
< avf
->nb_streams
; i
++) {
453 if (avf
->streams
[i
]->codecpar
->codec_type
== AVMEDIA_TYPE_VIDEO
) {
454 fifo_thread_ctx
.has_video_stream
= 1;
460 uint8_t just_flushed
= 0;
462 if (!fifo_thread_ctx
.recovery_nr
)
463 ret
= fifo_thread_dispatch_message(&fifo_thread_ctx
, &msg
);
465 if (ret
< 0 || fifo_thread_ctx
.recovery_nr
> 0) {
466 int rec_ret
= fifo_thread_recover(&fifo_thread_ctx
, &msg
, ret
);
468 av_thread_message_queue_set_err_send(queue
, rec_ret
);
473 /* If the queue is full at the moment when fifo_write_packet
474 * attempts to insert new message (packet) to the queue,
475 * it sets the fifo->overflow_flag to 1 and drops packet.
476 * Here in consumer thread, the flag is checked and if it is
477 * set, the queue is flushed and flag cleared. */
478 pthread_mutex_lock(&fifo
->overflow_flag_lock
);
479 if (fifo
->overflow_flag
) {
480 av_thread_message_flush(queue
);
481 if (fifo
->restart_with_keyframe
)
482 fifo_thread_ctx
.drop_until_keyframe
= 1;
483 fifo
->overflow_flag
= 0;
486 pthread_mutex_unlock(&fifo
->overflow_flag_lock
);
489 av_log(avf
, AV_LOG_INFO
, "FIFO queue flushed\n");
492 while (atomic_load_explicit(&fifo
->queue_duration
, memory_order_relaxed
) < fifo
->timeshift
)
495 ret
= av_thread_message_queue_recv(queue
, &msg
, 0);
497 av_thread_message_queue_set_err_send(queue
, ret
);
502 fifo
->write_trailer_ret
= fifo_thread_write_trailer(&fifo_thread_ctx
);
507 static int fifo_mux_init(AVFormatContext
*avf
, const AVOutputFormat
*oformat
,
508 const char *filename
)
510 FifoContext
*fifo
= avf
->priv_data
;
511 AVFormatContext
*avf2
;
514 ret
= avformat_alloc_output_context2(&avf2
, oformat
, NULL
, filename
);
520 avf2
->interrupt_callback
= avf
->interrupt_callback
;
521 avf2
->max_delay
= avf
->max_delay
;
522 ret
= av_dict_copy(&avf2
->metadata
, avf
->metadata
, 0);
525 avf2
->opaque
= avf
->opaque
;
526 avf2
->io_close2
= avf
->io_close2
;
527 avf2
->io_open
= avf
->io_open
;
528 avf2
->flags
= avf
->flags
;
530 for (i
= 0; i
< avf
->nb_streams
; ++i
) {
531 AVStream
*st
= ff_stream_clone(avf2
, avf
->streams
[i
]);
533 return AVERROR(ENOMEM
);
539 static int fifo_init(AVFormatContext
*avf
)
541 FifoContext
*fifo
= avf
->priv_data
;
542 const AVOutputFormat
*oformat
;
545 if (fifo
->recovery_wait_streamtime
&& !fifo
->drop_pkts_on_overflow
) {
546 av_log(avf
, AV_LOG_ERROR
, "recovery_wait_streamtime can be turned on"
547 " only when drop_pkts_on_overflow is also turned on\n");
548 return AVERROR(EINVAL
);
550 atomic_init(&fifo
->queue_duration
, 0);
551 fifo
->last_sent_dts
= AV_NOPTS_VALUE
;
554 /* This exists for the fifo_muxer test tool. */
555 if (fifo
->format
&& !strcmp(fifo
->format
, "fifo_test")) {
556 extern const FFOutputFormat ff_fifo_test_muxer
;
557 oformat
= &ff_fifo_test_muxer
.p
;
560 oformat
= av_guess_format(fifo
->format
, avf
->url
, NULL
);
562 ret
= AVERROR_MUXER_NOT_FOUND
;
566 ret
= fifo_mux_init(avf
, oformat
, avf
->url
);
570 ret
= av_thread_message_queue_alloc(&fifo
->queue
, (unsigned) fifo
->queue_size
,
571 sizeof(FifoMessage
));
575 av_thread_message_queue_set_free_func(fifo
->queue
, free_message
);
577 ret
= pthread_mutex_init(&fifo
->overflow_flag_lock
, NULL
);
580 fifo
->overflow_flag_lock_initialized
= 1;
585 static int fifo_write_header(AVFormatContext
*avf
)
587 FifoContext
* fifo
= avf
->priv_data
;
590 ret
= pthread_create(&fifo
->writer_thread
, NULL
, fifo_consumer_thread
, avf
);
592 av_log(avf
, AV_LOG_ERROR
, "Failed to start thread: %s\n",
593 av_err2str(AVERROR(ret
)));
600 static int fifo_write_packet(AVFormatContext
*avf
, AVPacket
*pkt
)
602 FifoContext
*fifo
= avf
->priv_data
;
603 FifoMessage msg
= {.type
= pkt
? FIFO_WRITE_PACKET
: FIFO_FLUSH_OUTPUT
};
607 ret
= av_packet_ref(&msg
.pkt
,pkt
);
612 ret
= av_thread_message_queue_send(fifo
->queue
, &msg
,
613 fifo
->drop_pkts_on_overflow
?
614 AV_THREAD_MESSAGE_NONBLOCK
: 0);
615 if (ret
== AVERROR(EAGAIN
)) {
616 uint8_t overflow_set
= 0;
618 /* Queue is full, set fifo->overflow_flag to 1
619 * to let consumer thread know the queue should
621 pthread_mutex_lock(&fifo
->overflow_flag_lock
);
622 if (!fifo
->overflow_flag
)
623 fifo
->overflow_flag
= overflow_set
= 1;
624 pthread_mutex_unlock(&fifo
->overflow_flag_lock
);
627 av_log(avf
, AV_LOG_WARNING
, "FIFO queue full\n");
630 } else if (ret
< 0) {
634 if (fifo
->timeshift
&& pkt
&& pkt
->dts
!= AV_NOPTS_VALUE
)
635 atomic_fetch_add_explicit(&fifo
->queue_duration
, next_duration(avf
, pkt
, &fifo
->last_sent_dts
), memory_order_relaxed
);
640 av_packet_unref(&msg
.pkt
);
644 static int fifo_write_trailer(AVFormatContext
*avf
)
646 FifoContext
*fifo
= avf
->priv_data
;
649 av_thread_message_queue_set_err_recv(fifo
->queue
, AVERROR_EOF
);
650 if (fifo
->timeshift
) {
651 int64_t now
= av_gettime_relative();
653 FifoMessage msg
= {FIFO_NOOP
};
655 int64_t delay
= av_gettime_relative() - now
;
656 if (delay
< 0) { // Discontinuity?
658 now
= av_gettime_relative();
662 atomic_fetch_add_explicit(&fifo
->queue_duration
, delay
, memory_order_relaxed
);
664 if (elapsed
> fifo
->timeshift
)
667 ret
= av_thread_message_queue_send(fifo
->queue
, &msg
, AV_THREAD_MESSAGE_NONBLOCK
);
668 } while (ret
>= 0 || ret
== AVERROR(EAGAIN
));
669 atomic_store(&fifo
->queue_duration
, INT64_MAX
);
672 ret
= pthread_join(fifo
->writer_thread
, NULL
);
674 av_log(avf
, AV_LOG_ERROR
, "pthread join error: %s\n",
675 av_err2str(AVERROR(ret
)));
679 ret
= fifo
->write_trailer_ret
;
683 static void fifo_deinit(AVFormatContext
*avf
)
685 FifoContext
*fifo
= avf
->priv_data
;
687 avformat_free_context(fifo
->avf
);
688 av_thread_message_queue_free(&fifo
->queue
);
689 if (fifo
->overflow_flag_lock_initialized
)
690 pthread_mutex_destroy(&fifo
->overflow_flag_lock
);
693 #define OFFSET(x) offsetof(FifoContext, x)
694 static const AVOption options
[] = {
695 {"attempt_recovery", "Attempt recovery in case of failure", OFFSET(attempt_recovery
),
696 AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM
},
698 {"drop_pkts_on_overflow", "Drop packets on fifo queue overflow not to block encoder", OFFSET(drop_pkts_on_overflow
),
699 AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM
},
701 {"fifo_format", "Target muxer", OFFSET(format
),
702 AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
704 {"format_opts", "Options to be passed to underlying muxer", OFFSET(format_options
),
705 AV_OPT_TYPE_DICT
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
707 {"max_recovery_attempts", "Maximal number of recovery attempts", OFFSET(max_recovery_attempts
),
708 AV_OPT_TYPE_INT
, {.i64
= FIFO_DEFAULT_MAX_RECOVERY_ATTEMPTS
}, 0, INT_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
710 {"queue_size", "Size of fifo queue", OFFSET(queue_size
),
711 AV_OPT_TYPE_INT
, {.i64
= FIFO_DEFAULT_QUEUE_SIZE
}, 1, INT_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
713 {"recovery_wait_streamtime", "Use stream time instead of real time while waiting for recovery",
714 OFFSET(recovery_wait_streamtime
), AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM
},
716 {"recovery_wait_time", "Waiting time between recovery attempts", OFFSET(recovery_wait_time
),
717 AV_OPT_TYPE_DURATION
, {.i64
= FIFO_DEFAULT_RECOVERY_WAIT_TIME_USEC
}, 0, INT64_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
719 {"recover_any_error", "Attempt recovery regardless of type of the error", OFFSET(recover_any_error
),
720 AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM
},
722 {"restart_with_keyframe", "Wait for keyframe when restarting output", OFFSET(restart_with_keyframe
),
723 AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM
},
725 {"timeshift", "Delay fifo output", OFFSET(timeshift
),
726 AV_OPT_TYPE_DURATION
, {.i64
= 0}, 0, INT64_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
731 static const AVClass fifo_muxer_class
= {
732 .class_name
= "Fifo muxer",
733 .item_name
= av_default_item_name
,
735 .version
= LIBAVUTIL_VERSION_INT
,
738 const FFOutputFormat ff_fifo_muxer
= {
740 .p
.long_name
= NULL_IF_CONFIG_SMALL("FIFO queue pseudo-muxer"),
741 .p
.priv_class
= &fifo_muxer_class
,
742 .p
.flags
= AVFMT_NOFILE
| AVFMT_TS_NEGATIVE
,
743 .priv_data_size
= sizeof(FifoContext
),
745 .write_header
= fifo_write_header
,
746 .write_packet
= fifo_write_packet
,
747 .write_trailer
= fifo_write_trailer
,
748 .deinit
= fifo_deinit
,
749 .flags_internal
= FF_OFMT_FLAG_ALLOW_FLUSH
,