mirror of
https://github.com/horgh/videostreamer.git
synced 2025-09-26 20:41:31 +08:00
Set options to try to address lag/freezing issues
The options I am going for are these: "-rtsp_transport", "udp", "-reset_timestamps", "1", "-vsync", "1", "-flags", "global_header", "-bsf:v", "dump_extra", This commit includes, I believe, all except `-reset_timestamps 1` and `-vsync 1`. For the first, I believe it is not applicable as we aren't segmenting, although I'm not sure. For the second, I believe again it is not applicable as we have only one stream. This is to address lag issues raised in #2. However, I'm unable to reproduce the issues locally, so this is to try and see if any of the options help.
This commit is contained in:
165
videostreamer.c
165
videostreamer.c
@@ -58,13 +58,34 @@ vs_open_input(const char * const input_format_name,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avformat_open_input(&input->format_ctx, input_url, input_format,
|
// Set rtsp_transport to udp. Whether this makes any real difference I'm not
|
||||||
NULL) != 0) {
|
// sure. I am debugging a lag/freeze issue.
|
||||||
printf("unable to open input\n");
|
AVDictionary * opts = NULL;
|
||||||
|
if (av_dict_set(&opts, "rtsp_transport", "udp", 0) < 0) {
|
||||||
|
printf("unable to set rtsp_transport opt\n");
|
||||||
vs_destroy_input(input);
|
vs_destroy_input(input);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (avformat_open_input(&input->format_ctx, input_url, input_format,
|
||||||
|
&opts) != 0) {
|
||||||
|
printf("unable to open input\n");
|
||||||
|
vs_destroy_input(input);
|
||||||
|
av_dict_free(&opts);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all options were found. If one wasn't then it will be returned
|
||||||
|
// and in the options.
|
||||||
|
if (av_dict_count(opts) != 0) {
|
||||||
|
printf("some options not set\n");
|
||||||
|
vs_destroy_input(input);
|
||||||
|
av_dict_free(&opts);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
av_dict_free(&opts);
|
||||||
|
|
||||||
if (avformat_find_stream_info(input->format_ctx, NULL) < 0) {
|
if (avformat_find_stream_info(input->format_ctx, NULL) < 0) {
|
||||||
printf("failed to find stream info\n");
|
printf("failed to find stream info\n");
|
||||||
vs_destroy_input(input);
|
vs_destroy_input(input);
|
||||||
@@ -147,6 +168,11 @@ vs_open_output(const char * const output_format_name,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try setting this option, again for lag/freezing issues. Although I am
|
||||||
|
// primarily trying to set the AV_CODEC_FLAG_GLOBAL_HEADER, so I don't know
|
||||||
|
// if this is applicable.
|
||||||
|
output_format->flags |= AVFMT_GLOBALHEADER;
|
||||||
|
|
||||||
if (avformat_alloc_output_context2(&output->format_ctx, output_format,
|
if (avformat_alloc_output_context2(&output->format_ctx, output_format,
|
||||||
NULL, NULL) < 0) {
|
NULL, NULL) < 0) {
|
||||||
printf("unable to create output context\n");
|
printf("unable to create output context\n");
|
||||||
@@ -174,6 +200,34 @@ vs_open_output(const char * const output_format_name,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I want to set a codec context flag. We can't do it directly to
|
||||||
|
// out_stream->codec as that is deprecated. Instead, allocate a codec context,
|
||||||
|
// copy the parameters, set the flag, then copy the codec context back to
|
||||||
|
// parameters. I'm not sure this is the correct way!
|
||||||
|
|
||||||
|
AVCodecContext * codec_ctx = avcodec_alloc_context3(NULL);
|
||||||
|
if (NULL == codec_ctx) {
|
||||||
|
printf("unable to allocate codec context\n");
|
||||||
|
vs_destroy_output(output);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avcodec_parameters_to_context(codec_ctx, out_stream->codecpar) < 0) {
|
||||||
|
printf("unable to copy codec parameters to codec context");
|
||||||
|
vs_destroy_output(output);
|
||||||
|
avcodec_free_context(&codec_ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||||
|
|
||||||
|
if (avcodec_parameters_from_context(out_stream->codecpar, codec_ctx) < 0) {
|
||||||
|
printf("unable to copy codec context to codec parameters");
|
||||||
|
vs_destroy_output(output);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
avcodec_free_context(&codec_ctx);
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
av_dump_format(output->format_ctx, 0, output_url, 1);
|
av_dump_format(output->format_ctx, 0, output_url, 1);
|
||||||
@@ -358,6 +412,74 @@ vs_write_packet(const struct VSInput * const input,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try using -bsf dump_extra. This is again trying to address lag/halting
|
||||||
|
// issues.
|
||||||
|
|
||||||
|
// TODO: We could keep a single bsf context around the entire time.
|
||||||
|
const AVBitStreamFilter * const bsf = av_bsf_get_by_name("dump_extra");
|
||||||
|
if (NULL == bsf) {
|
||||||
|
printf("unable to find bitstreamfilter dump_extra\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVBSFContext * bsf_ctx = NULL;
|
||||||
|
if (av_bsf_alloc(bsf, &bsf_ctx) < 0) {
|
||||||
|
printf("unable to allocate bsf context\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avcodec_parameters_copy(bsf_ctx->par_in, out_stream->codecpar) < 0) {
|
||||||
|
printf("unable to copy codec parameters (bsf input)\n");
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (avcodec_parameters_copy(bsf_ctx->par_out, out_stream->codecpar) < 0) {
|
||||||
|
printf("unable to copy codec parameters (bsf output)\n");
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (av_bsf_init(bsf_ctx) != 0) {
|
||||||
|
printf("unable to init bsf context\n");
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The BSF takes ownership of the packet. Since we don't have ownership here
|
||||||
|
// ourselves, clone it first.
|
||||||
|
AVPacket * pkt2 = av_packet_clone(pkt);
|
||||||
|
if (NULL == pkt2) {
|
||||||
|
printf("unable to clone packet\n");
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (av_bsf_send_packet(bsf_ctx, pkt2) != 0) {
|
||||||
|
printf("unable to send packet to bsf context\n");
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (av_bsf_receive_packet(bsf_ctx, pkt2) != 0) {
|
||||||
|
printf("unable to retrieve packet from bsf context\n");
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: There may be multiple packets to retrieve. We should fully drain the
|
||||||
|
// bsf.
|
||||||
|
|
||||||
|
// Signal we won't send any more packets.
|
||||||
|
if (av_bsf_send_packet(bsf_ctx, NULL) != 0) {
|
||||||
|
printf("unable to tell bsf context that no more packets will be sent\n");
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: After signaling no more packets, we should ensure we drain the bsf.
|
||||||
|
|
||||||
|
av_bsf_free(&bsf_ctx);
|
||||||
|
|
||||||
// It is possible that the input is not well formed. Its dts (decompression
|
// It is possible that the input is not well formed. Its dts (decompression
|
||||||
// timestamp) may fluctuate. av_write_frame() says that the dts must be
|
// timestamp) may fluctuate. av_write_frame() says that the dts must be
|
||||||
// strictly increasing.
|
// strictly increasing.
|
||||||
@@ -375,13 +497,13 @@ vs_write_packet(const struct VSInput * const input,
|
|||||||
// This is apparently a fairly common problem. In ffmpeg.c (as of ffmpeg
|
// This is apparently a fairly common problem. In ffmpeg.c (as of ffmpeg
|
||||||
// 3.2.4 at least) there is logic to rewrite the dts and warn if it happens.
|
// 3.2.4 at least) there is logic to rewrite the dts and warn if it happens.
|
||||||
// Let's do the same. Note my logic is a little different here.
|
// Let's do the same. Note my logic is a little different here.
|
||||||
if (pkt->dts != AV_NOPTS_VALUE && output->last_dts != AV_NOPTS_VALUE &&
|
if (pkt2->dts != AV_NOPTS_VALUE && output->last_dts != AV_NOPTS_VALUE &&
|
||||||
pkt->dts < output->last_dts) {
|
pkt2->dts < output->last_dts) {
|
||||||
int64_t const next_dts = output->last_dts+1;
|
int64_t const next_dts = output->last_dts+1;
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Warning: Non-monotonous DTS in input stream. Previous: %" PRId64 " current: %" PRId64 ". changing to %" PRId64 ".\n",
|
printf("Warning: Non-monotonous DTS in input stream. Previous: %" PRId64 " current: %" PRId64 ". changing to %" PRId64 ".\n",
|
||||||
output->last_dts, pkt->dts, next_dts);
|
output->last_dts, pkt2->dts, next_dts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We also apparently (ffmpeg.c does this too) need to update the pts.
|
// We also apparently (ffmpeg.c does this too) need to update the pts.
|
||||||
@@ -389,11 +511,11 @@ vs_write_packet(const struct VSInput * const input,
|
|||||||
//
|
//
|
||||||
// [mp4 @ 0x555e6825ea60] pts (3780) < dts (22531) in stream 0
|
// [mp4 @ 0x555e6825ea60] pts (3780) < dts (22531) in stream 0
|
||||||
|
|
||||||
if (pkt->pts != AV_NOPTS_VALUE && pkt->pts >= pkt->dts) {
|
if (pkt2->pts != AV_NOPTS_VALUE && pkt2->pts >= pkt2->dts) {
|
||||||
pkt->pts = FFMAX(pkt->pts, next_dts);
|
pkt2->pts = FFMAX(pkt2->pts, next_dts);
|
||||||
}
|
}
|
||||||
|
|
||||||
pkt->dts = next_dts;
|
pkt2->dts = next_dts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -404,39 +526,42 @@ vs_write_packet(const struct VSInput * const input,
|
|||||||
// the timestamps properly
|
// the timestamps properly
|
||||||
//
|
//
|
||||||
// [mp4 @ 0x55688397bc40] Encoder did not produce proper pts, making some up.
|
// [mp4 @ 0x55688397bc40] Encoder did not produce proper pts, making some up.
|
||||||
if (pkt->pts == AV_NOPTS_VALUE) {
|
if (pkt2->pts == AV_NOPTS_VALUE) {
|
||||||
pkt->pts = 0;
|
pkt2->pts = 0;
|
||||||
} else {
|
} else {
|
||||||
pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base,
|
pkt2->pts = av_rescale_q_rnd(pkt2->pts, in_stream->time_base,
|
||||||
out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pkt->dts == AV_NOPTS_VALUE) {
|
if (pkt2->dts == AV_NOPTS_VALUE) {
|
||||||
pkt->dts = 0;
|
pkt2->dts = 0;
|
||||||
} else {
|
} else {
|
||||||
pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base,
|
pkt2->dts = av_rescale_q_rnd(pkt2->dts, in_stream->time_base,
|
||||||
out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base,
|
pkt2->duration = av_rescale_q(pkt2->duration, in_stream->time_base,
|
||||||
out_stream->time_base);
|
out_stream->time_base);
|
||||||
pkt->pos = -1;
|
pkt2->pos = -1;
|
||||||
|
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
__vs_log_packet(output->format_ctx, pkt, "out");
|
__vs_log_packet(output->format_ctx, pkt2, "out");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Track last dts we see (see where we use it for why).
|
// Track last dts we see (see where we use it for why).
|
||||||
output->last_dts = pkt->dts;
|
output->last_dts = pkt2->dts;
|
||||||
|
|
||||||
|
|
||||||
// Write encoded frame (as a packet).
|
// Write encoded frame (as a packet).
|
||||||
|
|
||||||
// av_interleaved_write_frame() works too, but I don't think it is needed.
|
// av_interleaved_write_frame() works too, but I don't think it is needed.
|
||||||
// Using av_write_frame() skips buffering.
|
// Using av_write_frame() skips buffering.
|
||||||
const int write_res = av_write_frame(output->format_ctx, pkt);
|
const int write_res = av_write_frame(output->format_ctx, pkt2);
|
||||||
|
|
||||||
|
av_packet_unref(pkt2);
|
||||||
|
|
||||||
if (write_res != 0) {
|
if (write_res != 0) {
|
||||||
char error_buf[256];
|
char error_buf[256];
|
||||||
memset(error_buf, 0, 256);
|
memset(error_buf, 0, 256);
|
||||||
|
@@ -409,6 +409,10 @@ func (h HTTPHandler) streamRequest(rw http.ResponseWriter, r *http.Request) {
|
|||||||
rw.Header().Set("Content-Type", "video/mp4")
|
rw.Header().Set("Content-Type", "video/mp4")
|
||||||
rw.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
rw.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
|
||||||
|
// Apparently this can help Chrome. Although I have not seen issues without
|
||||||
|
// it.
|
||||||
|
rw.Header().Set("Accept-Ranges", "bytes")
|
||||||
|
|
||||||
// We send chunked by default
|
// We send chunked by default
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
Reference in New Issue
Block a user