Signed-off-by: Stephan Holljes <klaxa1...@googlemail.com> --- ffserver.c | 248 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 210 insertions(+), 38 deletions(-)
diff --git a/ffserver.c b/ffserver.c index f128b55..4f42f74 100644 --- a/ffserver.c +++ b/ffserver.c @@ -27,6 +27,9 @@ #include <unistd.h> #include <inttypes.h> #include <pthread.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> #include <libavutil/log.h> #include <libavutil/timestamp.h> @@ -48,6 +51,7 @@ struct ReadInfo { struct PublisherContext *pub; AVFormatContext *ifmt_ctx; char *input_uri; + char *server_name; }; struct WriteInfo { @@ -81,9 +85,12 @@ void *read_thread(void *arg) int id = 0; int64_t pts, now, start, last_cut = 0; int64_t *ts; + char playlist_dirname[1024]; + char playlist_filename[1024]; + AVFormatContext *ofmt_ctx[FMT_NB] = { 0 }; // some may be left unused struct Segment *seg = NULL; AVPacket pkt; - AVStream *in_stream; + AVStream *in_stream, *out_stream; AVRational tb = {1, AV_TIME_BASE}; AVStream *stream; @@ -104,6 +111,95 @@ void *read_thread(void *arg) if (video_idx == -1) audio_only = 1; + if (stream_formats[FMT_HLS] || stream_formats[FMT_DASH]) { + snprintf(playlist_dirname, 1024, "%s/%s", info->server_name, info->config->stream_name); + ret = mkdir(playlist_dirname, 0755); + if (ret < 0 && errno != EEXIST) { + av_log(NULL, AV_LOG_WARNING, "Could not create stream directory (%s) dropping hls/dash\n", strerror(errno)); + stream_formats[FMT_HLS] = 0; + stream_formats[FMT_DASH] = 0; + } + } + + if (info->pub->stream_formats[FMT_HLS]) { + snprintf(playlist_filename, 1024, "%s/%s/%s_hls.m3u8", info->server_name, info->pub->stream_name, + info->pub->stream_name); + avformat_alloc_output_context2(&ofmt_ctx[FMT_HLS], NULL, "hls", playlist_filename); + + if (!ofmt_ctx[FMT_HLS]) { + av_log(NULL, AV_LOG_ERROR, "Could not allocate hls output context.\n"); + goto end; + } + + for (i = 0; i < ifmt_ctx->nb_streams; i++) { + in_stream = ifmt_ctx->streams[i]; + out_stream = avformat_new_stream(ofmt_ctx[FMT_HLS], NULL); + if (!out_stream) { + av_log(ofmt_ctx[FMT_HLS], AV_LOG_WARNING, "Failed allocating output stream\n"); + continue; + } + ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); + if (ret < 0) { + av_log(ofmt_ctx[FMT_HLS], AV_LOG_WARNING, "Failed to copy context from input to output stream codec context\n"); + continue; + } + out_stream->codecpar->codec_tag = 0; + if (out_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (in_stream->sample_aspect_ratio.num) + out_stream->sample_aspect_ratio = in_stream->sample_aspect_ratio; + out_stream->avg_frame_rate = in_stream->avg_frame_rate; + out_stream->r_frame_rate = in_stream->r_frame_rate; + } + av_dict_copy(&out_stream->metadata, in_stream->metadata, 0); + } + av_dict_copy(&ofmt_ctx[FMT_HLS]->metadata, ifmt_ctx->metadata, 0); + ret = avformat_write_header(ofmt_ctx[FMT_HLS], NULL); + if (ret < 0) { + av_log(ofmt_ctx[FMT_HLS], AV_LOG_WARNING, "Error occured while writing header: %s\n", av_err2str(ret)); + } + + av_log(ofmt_ctx[FMT_HLS], AV_LOG_DEBUG, "Initialized hls.\n"); + } + + if (info->pub->stream_formats[FMT_DASH]) { + snprintf(playlist_filename, 1024, "%s/%s/%s_dash.mpd", info->server_name, info->pub->stream_name, + info->pub->stream_name); + avformat_alloc_output_context2(&ofmt_ctx[FMT_DASH], NULL, "dash", playlist_filename); + + if (!ofmt_ctx[FMT_DASH]) { + av_log(NULL, AV_LOG_ERROR, "Could not allocate hls output context.\n"); + goto end; + } + + for (i = 0; i < ifmt_ctx->nb_streams; i++) { + in_stream = ifmt_ctx->streams[i]; + out_stream = avformat_new_stream(ofmt_ctx[FMT_DASH], NULL); + if (!out_stream) { + av_log(ofmt_ctx[FMT_DASH], AV_LOG_WARNING, "Failed allocating output stream\n"); + continue; + } + ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); + if (ret < 0) { + av_log(ofmt_ctx[FMT_DASH], AV_LOG_WARNING, "Failed to copy context from input to output stream codec context\n"); + continue; + } + out_stream->codecpar->codec_tag = 0; + if (out_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (in_stream->sample_aspect_ratio.num) + out_stream->sample_aspect_ratio = in_stream->sample_aspect_ratio; + out_stream->avg_frame_rate = in_stream->avg_frame_rate; + out_stream->r_frame_rate = in_stream->r_frame_rate; + } + av_dict_copy(&out_stream->metadata, in_stream->metadata, 0); + } + av_dict_copy(&ofmt_ctx[FMT_DASH]->metadata, ifmt_ctx->metadata, 0); + ret = avformat_write_header(ofmt_ctx[FMT_DASH], NULL); + if (ret < 0) { + av_log(ofmt_ctx[FMT_DASH], AV_LOG_WARNING, "Error occured while writing header: %s\n", av_err2str(ret)); + } + + av_log(ofmt_ctx[FMT_DASH], AV_LOG_DEBUG, "Initialized dash.\n"); + } // All information needed to start segmenting the file is gathered now. // start BUFFER_SECS seconds "in the past" to "catch up" to real-time. Has no effect on streamed sources. @@ -141,45 +237,86 @@ void *read_thread(void *arg) now = av_gettime_relative() - start; } - // keyframe or first Segment or audio_only and more than AUDIO_ONLY_SEGMENT_SECONDS passed since last cut - if ((pkt.flags & AV_PKT_FLAG_KEY && pkt.stream_index == video_idx) || !seg || - (audio_only && pts - last_cut >= AUDIO_ONLY_SEGMENT_SECONDS * AV_TIME_BASE)) { - if (seg) { - segment_close(seg); - publisher_push_segment(info->pub, seg); - av_log(NULL, AV_LOG_DEBUG, "New segment pushed.\n"); - publish(info->pub); - av_log(NULL, AV_LOG_DEBUG, "Published new segment.\n"); + if (info->pub->stream_formats[FMT_HLS]) { + ret = av_write_frame(ofmt_ctx[FMT_HLS], &pkt); + if (ret < 0) { + fprintf(stderr, "Error muxing packet\n"); + break; + } + } + if (info->pub->stream_formats[FMT_MATROSKA]) { + // keyframe or first Segment or audio_only and more than AUDIO_ONLY_SEGMENT_SECONDS passed since last cut + if ((pkt.flags & AV_PKT_FLAG_KEY && pkt.stream_index == video_idx) || !seg || + (audio_only && pts - last_cut >= AUDIO_ONLY_SEGMENT_SECONDS * AV_TIME_BASE)) { + if (seg) { + segment_close(seg); + publisher_push_segment(info->pub, seg); + av_log(NULL, AV_LOG_DEBUG, "New segment pushed.\n"); + publish(info->pub); + av_log(NULL, AV_LOG_DEBUG, "Published new segment.\n"); + } + last_cut = pts; + segment_init(&seg, ifmt_ctx); + if (!seg) { + av_log(NULL, AV_LOG_ERROR, "Segment initialization failed, shutting down.\n"); + goto end; + } + seg->id = id++; + av_log(NULL, AV_LOG_DEBUG, "Starting new segment, id: %d\n", seg->id); + } + + ts = av_dynarray2_add((void **)&seg->ts, &seg->ts_len, sizeof(int64_t), + (const void *)&pkt.dts); + if (!ts) { + av_log(seg->fmt_ctx, AV_LOG_ERROR, "could not write dts\n."); + goto end; + } + + ts = av_dynarray2_add((void **)&seg->ts, &seg->ts_len, sizeof(int64_t), + (const void *)&pkt.pts); + if (!ts) { + av_log(seg->fmt_ctx, AV_LOG_ERROR, "could not write pts\n."); + goto end; } - last_cut = pts; - segment_init(&seg, ifmt_ctx); - if (!seg) { - av_log(NULL, AV_LOG_ERROR, "Segment initialization failed, shutting down.\n"); + ret = av_write_frame(seg->fmt_ctx, &pkt); + if (ret < 0) { + av_log(seg->fmt_ctx, AV_LOG_ERROR, "av_write_frame() failed.\n"); goto end; } - seg->id = id++; - av_log(NULL, AV_LOG_DEBUG, "Starting new segment, id: %d\n", seg->id); } - ts = av_dynarray2_add((void **)&seg->ts, &seg->ts_len, sizeof(int64_t), - (const void *)&pkt.dts); - if (!ts) { - av_log(seg->fmt_ctx, AV_LOG_ERROR, "could not write dts\n."); - goto end; + if (info->pub->stream_formats[FMT_DASH]) { + pts_tmp = pkt.pts; + dts_tmp = pkt.dts; + pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, ofmt_ctx[FMT_DASH]->streams[pkt.stream_index]->time_base, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, ofmt_ctx[FMT_DASH]->streams[pkt.stream_index]->time_base, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + pkt.duration = av_rescale_q_rnd(pkt.duration, in_stream->time_base, ofmt_ctx[FMT_DASH]->streams[pkt.stream_index]->time_base, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + + ret = av_write_frame(ofmt_ctx[FMT_DASH], &pkt); + pkt.pts = pts_tmp; + pkt.dts = dts_tmp; + if (ret < 0) { + fprintf(stderr, "Error muxing packet\n"); + break; + } } - ts = av_dynarray2_add((void **)&seg->ts, &seg->ts_len, sizeof(int64_t), - (const void *)&pkt.pts); - if (!ts) { - av_log(seg->fmt_ctx, AV_LOG_ERROR, "could not write pts\n."); - goto end; + if (info->pub->stream_formats[FMT_HLS]) { + pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, ofmt_ctx[FMT_HLS]->streams[pkt.stream_index]->time_base, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, ofmt_ctx[FMT_HLS]->streams[pkt.stream_index]->time_base, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + + ret = av_write_frame(ofmt_ctx[FMT_HLS], &pkt); + if (ret < 0) { + fprintf(stderr, "Error muxing packet\n"); + break; + } } - ret = av_write_frame(seg->fmt_ctx, &pkt); av_packet_unref(&pkt); - if (ret < 0) { - av_log(seg->fmt_ctx, AV_LOG_ERROR, "av_write_frame() failed.\n"); - goto end; - } } if (ret < 0 && ret != AVERROR_EOF) { @@ -195,6 +332,12 @@ void *read_thread(void *arg) end: avformat_close_input(&ifmt_ctx); info->pub->shutdown = 1; + for (i = 0; i < FMT_NB; i++) { + if (ofmt_ctx[i]) { + av_write_trailer(ofmt_ctx[i]); + avformat_free_context(ofmt_ctx[i]); + } + } return NULL; } @@ -338,7 +481,7 @@ void *accept_thread(void *arg) av_log(server, AV_LOG_DEBUG, "Accepting new clients.\n"); reply_code = 200; - if ((ret = info->httpd->accept(server, &client, reply_code)) < 0) { + if ((ret = info->httpd->accept(server, &client, NULL)) < 0) { if (ret == HTTPD_LISTEN_TIMEOUT) { continue; } else if (ret == HTTPD_CLIENT_ERROR) { @@ -352,12 +495,23 @@ void *accept_thread(void *arg) ifmt_ctx = NULL; for (i = 0; i < config->nb_streams; i++) { stream_name = info->pubs[i]->stream_name; - // skip leading '/' ---v - if (client->resource && strlen(client->resource) - && !strncmp(client->resource + 1, stream_name, strlen(stream_name))) { - pub = info->pubs[i]; - ifmt_ctx = info->ifmt_ctxs[i]; - break; + // skip leading '/' ---v + if (resource && strlen(resource) > strlen(stream_name) + && !strncmp(resource + 1, stream_name, strlen(stream_name))) { + resource++; + while (resource && *resource++ != '/'); + if (strlen(resource) > 2 && !strncmp(resource, "mkv", 3)) { + pub = info->pubs[i]; + ifmt_ctx = info->ifmt_ctxs[i]; + memset(requested_file, 0, 1024); + break; + } else if (strlen(resource) > 2 && !strncmp(resource, "hls", 3)) { + snprintf(requested_file, 1024, "/%s/%s_hls.m3u8", stream_name, stream_name); + break; + } else if (strlen(resource) > 3 && !strncmp(resource, "dash", 4)) { + snprintf(requested_file, 1024, "/%s/%s_dash.mpd", stream_name, stream_name); + break; + } } } @@ -544,6 +698,23 @@ void *run_server(void *arg) { goto error_cleanup; } + for (stream_index = 0; stream_index < config->nb_streams; stream_index++) { + for (i = 0; i < config->streams[stream_index].nb_formats; i++) + stream_formats[config->streams[stream_index].formats[i]] = 1; + } + + if (stream_formats[FMT_HLS] || stream_formats[FMT_DASH]) { + fileserver_init(&fs, config->server_name); + ret = mkdir(config->server_name, 0755); + if (ret < 0 && errno != EEXIST) { + av_log(NULL, AV_LOG_WARNING, "Could not create server directory (%d) dropping hls/dash\n", errno); + stream_formats[FMT_HLS] = 0; + stream_formats[FMT_DASH] = 0; + fileserver_free(fs); + fs = NULL; + } + } + av_log_set_level(AV_LOG_INFO); ainfo.pubs = pubs; @@ -581,6 +752,7 @@ void *run_server(void *arg) { pthread_t *w_threads = NULL; pthread_t r_thread; rinfo.input_uri = config->streams[stream_index].input_uri; + rinfo.server_name = config->server_name; if ((ret = avformat_open_input(&ifmt_ctx, rinfo.input_uri, NULL, NULL))) { av_log(NULL, AV_LOG_ERROR, "run_server: Could not open input\n"); -- 2.18.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel