From 4247a7b81e8e8a1b4c695d9cdf95144efbf66c0a Mon Sep 17 00:00:00 2001
From: Palana <palana@stunned.de>
Date: Wed, 3 Sep 2014 04:10:44 +0200
Subject: [PATCH] Add media remuxer to media-io

---
 libobs/CMakeLists.txt         |   6 +-
 libobs/media-io/media-remux.c | 251 ++++++++++++++++++++++++++++++++++
 libobs/media-io/media-remux.h |  39 ++++++
 3 files changed, 294 insertions(+), 2 deletions(-)
 create mode 100644 libobs/media-io/media-remux.c
 create mode 100644 libobs/media-io/media-remux.h

diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt
index ffc43bad9..2f5120f05 100644
--- a/libobs/CMakeLists.txt
+++ b/libobs/CMakeLists.txt
@@ -143,7 +143,8 @@ set(libobs_mediaio_SOURCES
 	media-io/video-frame.c
 	media-io/format-conversion.c
 	media-io/audio-resampler-ffmpeg.c
-	media-io/video-scaler-ffmpeg.c)
+	media-io/video-scaler-ffmpeg.c
+	media-io/media-remux.c)
 set(libobs_mediaio_HEADERS
 	media-io/media-io-defs.h
 	media-io/video-io.h
@@ -151,7 +152,8 @@ set(libobs_mediaio_HEADERS
 	media-io/video-frame.h
 	media-io/format-conversion.h
 	media-io/audio-resampler.h
-	media-io/video-scaler.h)
+	media-io/video-scaler.h
+	media-io/media-remux.h)
 
 set(libobs_util_SOURCES
 	util/array-serializer.c
diff --git a/libobs/media-io/media-remux.c b/libobs/media-io/media-remux.c
new file mode 100644
index 000000000..a6652eeb5
--- /dev/null
+++ b/libobs/media-io/media-remux.c
@@ -0,0 +1,251 @@
+/******************************************************************************
+    Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#include "media-remux.h"
+
+#include "../util/base.h"
+#include "../util/bmem.h"
+#include "../util/platform.h"
+
+#include <libavformat/avformat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+struct media_remux_job {
+	int64_t in_size;
+	AVFormatContext *ifmt_ctx, *ofmt_ctx;
+};
+
+static inline void init_size(media_remux_job_t job, const char *in_filename)
+{
+#ifdef _MSC_VER
+	struct _stat64 st = {0};
+	_stat64(in_filename, &st);
+#else
+	struct stat st = {0};
+	stat(in_filename, &st);
+#endif
+	job->in_size = st.st_size;
+}
+
+static inline bool init_input(media_remux_job_t job, const char *in_filename)
+{
+	int ret = avformat_open_input(&job->ifmt_ctx, in_filename, NULL, NULL);
+	if (ret < 0) {
+		blog(LOG_ERROR, "media_remux: Could not open input file '%s'",
+				in_filename);
+		return false;
+	}
+
+	ret = avformat_find_stream_info(job->ifmt_ctx, NULL);
+	if (ret < 0) {
+		blog(LOG_ERROR, "media_remux: Failed to retrieve input stream"
+				" information");
+		return false;
+	}
+
+#ifndef _NDEBUG
+	av_dump_format(job->ifmt_ctx, 0, in_filename, false);
+#endif
+	return true;
+}
+
+static inline bool init_output(media_remux_job_t job, const char *out_filename)
+{
+	int ret;
+
+	avformat_alloc_output_context2(&job->ofmt_ctx, NULL, NULL,
+			out_filename);
+	if (!job->ofmt_ctx) {
+		blog(LOG_ERROR, "media_remux: Could not create output context");
+		return false;
+	}
+
+	for (unsigned i = 0; i < job->ifmt_ctx->nb_streams; i++) {
+		AVStream *in_stream  = job->ifmt_ctx->streams[i];
+		AVStream *out_stream = avformat_new_stream(job->ofmt_ctx,
+				in_stream->codec->codec);
+		if (!out_stream) {
+			blog(LOG_ERROR, "media_remux: Failed to allocate output"
+					" stream");
+			return false;
+		}
+
+		ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
+		if (ret < 0) {
+			blog(LOG_ERROR, "media_remux: Failed to copy context");
+			return false;
+		}
+		out_stream->time_base = out_stream->codec->time_base;
+
+		out_stream->codec->codec_tag = 0;
+		if (job->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
+			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
+	}
+
+#ifndef _NDEBUG
+	av_dump_format(job->ofmt_ctx, 0, out_filename, true);
+#endif
+
+	if (!(job->ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
+		ret = avio_open(&job->ofmt_ctx->pb, out_filename,
+				AVIO_FLAG_WRITE);
+		if (ret < 0) {
+			blog(LOG_ERROR, "media_remux: Failed to open output"
+					" file '%s'", out_filename);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+bool media_remux_job_create(media_remux_job_t *job, const char *in_filename,
+		const char *out_filename)
+{
+	if (!job)
+		return false;
+
+	*job = NULL;
+	if (!os_file_exists(in_filename))
+		return false;
+
+	*job = (media_remux_job_t)bzalloc(sizeof(struct media_remux_job));
+	if (!*job)
+		return false;
+
+	init_size(*job, in_filename);
+
+	av_register_all();
+
+	if (!init_input(*job, in_filename))
+		goto fail;
+
+	if (!init_output(*job, out_filename))
+		goto fail;
+
+	return true;
+
+fail:
+	media_remux_job_destroy(*job);
+	return false;
+}
+
+static inline void process_packet(AVPacket *pkt,
+		AVStream *in_stream, AVStream *out_stream)
+{
+	pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base,
+			out_stream->time_base,
+			AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
+	pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base,
+			out_stream->time_base,
+			AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
+	pkt->duration = (int)av_rescale_q(pkt->duration,
+			in_stream->time_base, out_stream->time_base);
+	pkt->pos = -1;
+
+}
+
+static inline int process_packets(media_remux_job_t job,
+		media_remux_progress_callback callback, void *data)
+{
+	AVPacket pkt;
+
+	int ret, throttle = 0;
+	for (;;) {
+		ret = av_read_frame(job->ifmt_ctx, &pkt);
+		if (ret < 0) {
+			if (ret != AVERROR_EOF)
+				blog(LOG_ERROR, "media_remux: Error reading"
+						" packet: %s",
+						av_err2str(ret));
+			break;
+		}
+
+		if (callback != NULL && throttle++ > 10) {
+			float progress = pkt.pos / (float)job->in_size * 100.f;
+			if (!callback(data, progress))
+				break;
+			throttle = 0;
+		}
+
+		process_packet(&pkt, job->ifmt_ctx->streams[pkt.stream_index],
+				job->ofmt_ctx->streams[pkt.stream_index]);
+
+		ret = av_interleaved_write_frame(job->ofmt_ctx, &pkt);
+		av_free_packet(&pkt);
+
+		if (ret < 0) {
+			blog(LOG_ERROR, "media_remux: Error muxing packet: %s",
+					av_err2str(ret));
+			break;
+		}
+	}
+
+	return ret;
+}
+
+bool media_remux_job_process(media_remux_job_t job,
+		media_remux_progress_callback callback, void *data)
+{
+	int ret;
+	bool success = false;
+
+	if (!job)
+		return success;
+
+	ret = avformat_write_header(job->ofmt_ctx, NULL);
+	if (ret < 0) {
+		blog(LOG_ERROR, "media_remux: Error opening output file: %s",
+				av_err2str(ret));
+		return success;
+	}
+
+	if (callback != NULL)
+		callback(data, 0.f);
+
+	ret = process_packets(job, callback, data);
+	success = ret >= 0 || ret == AVERROR_EOF;
+
+	ret = av_write_trailer(job->ofmt_ctx);
+	if (ret < 0) {
+		blog(LOG_ERROR, "media_remux: av_write_trailer: %s",
+				av_err2str(ret));
+		success = false;
+	}
+
+	if (callback != NULL)
+		callback(data, 100.f);
+
+	return success;
+}
+
+void media_remux_job_destroy(media_remux_job_t job)
+{
+	if (!job)
+		return;
+
+	avformat_close_input(&job->ifmt_ctx);
+
+	if (job->ofmt_ctx && !(job->ofmt_ctx->oformat->flags & AVFMT_NOFILE))
+		avio_close(job->ofmt_ctx->pb);
+
+	avformat_free_context(job->ofmt_ctx);
+
+	bfree(job);
+}
diff --git a/libobs/media-io/media-remux.h b/libobs/media-io/media-remux.h
new file mode 100644
index 000000000..0ebdcae17
--- /dev/null
+++ b/libobs/media-io/media-remux.h
@@ -0,0 +1,39 @@
+/******************************************************************************
+    Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#include "../util/c99defs.h"
+
+#pragma once
+
+struct media_remux_job;
+typedef struct media_remux_job *media_remux_job_t;
+
+typedef bool (media_remux_progress_callback)(void *data, float percent);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+EXPORT bool media_remux_job_create(media_remux_job_t *job,
+		const char *in_filename, const char *out_filename);
+EXPORT bool media_remux_job_process(media_remux_job_t job,
+		media_remux_progress_callback callback, void *data);
+EXPORT void media_remux_job_destroy(media_remux_job_t job);
+
+#ifdef __cplusplus
+}
+#endif
-- 
GitLab