From patchwork Thu Jun 28 00:51:12 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephan Holljes X-Patchwork-Id: 9540 Delivered-To: ffmpegpatchwork@gmail.com Received: by 2002:a02:104:0:0:0:0:0 with SMTP id c4-v6csp1479641jad; Wed, 27 Jun 2018 17:52:56 -0700 (PDT) X-Google-Smtp-Source: AAOMgpf97TUqOxwIAAwM7aRb8QcsIkz3qnP4XPSWNRs3ogpl+xY9+cqMeTlIqWZD/s+hfj8gCiH0 X-Received: by 2002:adf:9603:: with SMTP id b3-v6mr6948506wra.253.1530147176375; Wed, 27 Jun 2018 17:52:56 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1530147176; cv=none; d=google.com; s=arc-20160816; b=ryCZUgiojguL4l2BJb22yATjfgXFz2GkodMGOq+mJsEhhjgaazvLzE7816FSn4akae ZHTV/YKtmICIAN2x3URIg3ufJCbMSgDedjCo3gb62YRLb38aemsxi4fbgKNJUWbqWOgv XhzRVxK7mWi6HJKJbzJBmPaBc7/AztKevtrplj62AAWuNIbqFXHwvpaRf+oyqC0E7MNp RiC2kGw0JqTbS0JyAriN5n0bgs66537wrj6NCGaEH+7QN2TQiYayrG968UqDGXojJwD4 ZbEM7uc+chOTDXZdGJskkloO5sEjVqW1PAYjfoJ2nbV5HdnJKNehY6TjSHQm3U+/9UHY KdIg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:references:in-reply-to:message-id:date :to:from:dkim-signature:delivered-to:arc-authentication-results; bh=ZW0e3BJxTt+pHZdmaHNzu51Cp46qAncjJDVdcloDzG4=; b=v8W8dkaIF7tTWNAYhqUrrNlTpImCwveiB2gb9D4Ub+b/fSYuRBaERyR+9Gln6E64O+ IYdj292vBc7okZXtrGz29tiTbuI+ARBsnnXrv4C4rYkUBMKUGDplSUfIDz10qsSE51XG pw7+WGwWHSbFtxO4QUrCMJ6uKntuJJnn7ztuzkZKKJVnkZkccVpfTWSjwlrimTfMNW5J F0qKeUrm0RSowDLB2jOoKwxAagk3EsY40NyOAZslY5UHon0FlNsYcpUJVgRW9wdlImuh 14MRXspbK7yywrzFfr4mPon53l8uiKU2AJmNZuYSoQDuSLo1wF+PEowEII5Jvb2qQe62 LzMw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@googlemail.com header.s=20161025 header.b=bMj+dyc1; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=googlemail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id g135-v6si3229306wmg.143.2018.06.27.17.52.56; Wed, 27 Jun 2018 17:52:56 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@googlemail.com header.s=20161025 header.b=bMj+dyc1; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=googlemail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 4BE5168A591; Thu, 28 Jun 2018 03:51:43 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wm0-f67.google.com (mail-wm0-f67.google.com [74.125.82.67]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 41EB768A380 for ; Thu, 28 Jun 2018 03:51:41 +0300 (EEST) Received: by mail-wm0-f67.google.com with SMTP id v16-v6so7684743wmv.5 for ; Wed, 27 Jun 2018 17:51:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlemail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=AkWeHrPVlbHX7g/r1FSWsJzcR2dsfMiNa/Z9LtMrs48=; b=bMj+dyc1pwLWVrel0WgyYngV9NwFV2oJq6E8C9LXDvpv23vGYHC10iAv1zfLYEztg5 NXOVg1gh6NZXLzyZwco21yFLKjwIqMZm9UawWEKFgayA7FUg+QdozNLZZWF/pnxD3xmC fNa4s+OgmImn0fUD1rsBWC1KTbrsvuiJWYfcV5dI1q1Nj9JS7Iv2hQn5O9/PrFv/rNAg kFohc/QtBY9K+9Y4ZS7KIEaz+NKoA5FX+Bb2CWyValGBvwQKNiO4p3hlKWZuaTMYjG7s GtYqXdXa2CuylWiCPxlne41z34GmfumHBOTZGMF8A8AR3kGhKclRWX4WRQFCnsgixnPz Qt2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=AkWeHrPVlbHX7g/r1FSWsJzcR2dsfMiNa/Z9LtMrs48=; b=sAty0Vo9kK+G9otfUgNhKIATIyGl3tRvAmCNA0idejf63gpMTqjsIWo9ldYYBdiwz+ 2ecZQpw1uxY+pzy9wyP6+xkvX1mN81xdwp2gRhA0H055x4shDW/9d0m7nzi1X1vAmKu+ YjDm055vJl5oAhnlh8B9BJXfX1dZAhaqf+Te5YU8F0UEBWUlhZwF5DwFFF5duJZOxOr3 BO39m+XxqYjhw+hmhQO89IPIbWXp2rt9NxxKzJKhEtO/woFa3Dj24BdU8VLJubZDWA3y ccyCwqnwYF3kJKdeYNZ0bVVlCDa+DD+lmtQB4vjmPZBMyt55rWJ/oZz+CxMNFtIRbX2L 0sZw== X-Gm-Message-State: APt69E3w5fmkfzNPKcesdgj96ZQdHqhp4uoR8s3iztfCkGGKJGO1Q1tz XhUq/6z5KbduieQYvK/oZRqMwg== X-Received: by 2002:a1c:4adb:: with SMTP id n88-v6mr6525540wmi.121.1530147100902; Wed, 27 Jun 2018 17:51:40 -0700 (PDT) Received: from localhost.localdomain ([46.5.2.0]) by smtp.gmail.com with ESMTPSA id a9-v6sm1363017wrq.1.2018.06.27.17.51.39 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 27 Jun 2018 17:51:40 -0700 (PDT) From: Stephan Holljes To: ffmpeg-devel@ffmpeg.org Date: Thu, 28 Jun 2018 02:51:12 +0200 Message-Id: <20180628005117.18902-13-klaxa1337@googlemail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180628005117.18902-1-klaxa1337@googlemail.com> References: <20180628005117.18902-1-klaxa1337@googlemail.com> Subject: [FFmpeg-devel] [PATCH 12/17] ffserver.c: Add hls and dash and adapt to new httpd interface (stub) X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Stephan Holljes MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Stephan Holljes --- 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 #include #include +#include +#include +#include #include #include @@ -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");