summaryrefslogtreecommitdiff
path: root/transmuxer.c
diff options
context:
space:
mode:
authorbbergeron <[email protected]>2024-04-03 17:32:01 -0400
committerbbergeron <[email protected]>2024-04-03 17:32:01 -0400
commitc1cb78d574c0429aa5e3ff3a2b3886e4bc153212 (patch)
treebf68806bcbddcafafc015b28c25550ea457eeecc /transmuxer.c
Reset Git repo and use a pseudonym to sign commits
I used to sign my commits with my real name and my personal email address, which I wanted scrubbed off the "B." pseudosphere. Re-creating a new git repository was safer than simpler than re-writing the history (although the latter could've also worked but, oh well).
Diffstat (limited to 'transmuxer.c')
-rw-r--r--transmuxer.c283
1 files changed, 283 insertions, 0 deletions
diff --git a/transmuxer.c b/transmuxer.c
new file mode 100644
index 0000000..9dcf005
--- /dev/null
+++ b/transmuxer.c
@@ -0,0 +1,283 @@
+#include <asm-generic/errno-base.h>
+#include <assert.h>
+#include <libavcodec/avcodec.h>
+#include <libavcodec/codec.h>
+#include <libavcodec/codec_id.h>
+#include <libavcodec/codec_par.h>
+#include <libavcodec/packet.h>
+#include <libavformat/avformat.h>
+#include <libavutil/avutil.h>
+#include <libavutil/channel_layout.h>
+#include <libavutil/dict.h>
+#include <libavutil/error.h>
+#include <libavutil/frame.h>
+#include <libavutil/log.h>
+#include <libavutil/mathematics.h>
+#include <libavutil/opt.h>
+#include <libavutil/rational.h>
+#include <libavutil/samplefmt.h>
+#include <libswresample/swresample.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include "audio/muxer.h"
+#include "common.h"
+#include "transmuxer.h"
+
+#define NANOSEC (uint64_t)1000000000
+#define MIN(a, b) ((a < b) ? a : b)
+
+enum PacketType {
+ PKT_AUDIO,
+ PKT_VIDEO,
+};
+
+struct HLSRemuxer {
+ enum State state;
+ pthread_cond_t resume;
+
+ /* Input objects */
+ AVFormatContext *demuxer;
+ const AVStream *audio_stream;
+ const AVStream *video_stream;
+ int audio_stream_index;
+ int video_stream_index;
+
+ /* Output object pointers */
+ long long next_sample;
+ AVFormatContext *hls_writer;
+ AudioMuxer *audio_muxer;
+
+ struct timespec start_time;
+
+ /* overhead: The target lenght in nanosecond of the output's stream
+ * buffer.
+ */
+ uint64_t overhead;
+};
+
+static void hls_nanosleep (uint64_t t);
+static uint64_t hls_nanotime (void);
+static int hls_remuxer_init_input (struct HLSRemuxer *remuxer, const char *file);
+
+static void timespec_add_nano (struct timespec *ts, uint64_t nanoseconds);
+static int timespec_is_ahead (const struct timespec *a, const struct timespec *b);
+static int remuxer_send_audio (HLSRemuxer *remuxer, AVPacket *audio_packet);
+static int remuxer_send_video (HLSRemuxer *remuxer, AVPacket *audio_packet);
+static int remuxer_wait_target_ts (HLSRemuxer *remuxer);
+static int remuxer_get_overrun (HLSRemuxer *remuxer, struct timespec *until);
+
+HLSRemuxer *hls_remuxer_init (const struct StreamerOpt *opts)
+{
+ AVStream *stream;
+ HLSRemuxer *remuxer;
+ AVFormatContext *hls_muxer;
+ AudioMuxer *audio_muxer;
+ AVDictionary *m;
+
+ if ((remuxer = malloc(sizeof(HLSRemuxer))) == NULL)
+ return NULL;
+
+ av_log_set_level(AV_LOG_VERBOSE);
+
+ int err;
+
+ if ((err = avformat_alloc_output_context2(&hls_muxer, NULL, "hls", opts->filename)) < 0) {
+ fprintf(stderr, "Failed to allocate output context: %s\n", av_err2str(err));
+ return NULL;
+ }
+
+ struct AudioMuxerOpt audio_opts = {
+ .codec_id = AV_CODEC_ID_MP3,
+ .sample_rate = 44100,
+ .sample_format = AV_SAMPLE_FMT_S16P,
+ .opts_encoder = NULL,
+ .opts_muxer = NULL,
+ .ch_layout = AV_CHANNEL_LAYOUT_STEREO
+ };
+
+ if (audiomuxer_init(&audio_muxer, hls_muxer, &audio_opts))
+ goto error;
+
+ // TODO: Pass muxer options
+ if ((err = avformat_write_header(hls_muxer, NULL)) < 0) {
+ fprintf(stderr, "Failed to write output file: %s\n", av_err2str(err));
+ goto error;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &remuxer->start_time);
+ remuxer->hls_writer = hls_muxer;
+ remuxer->audio_muxer = audio_muxer;
+ remuxer->overhead = 10 * NANOSEC; // NOTE: hls_time * hls_list_size * NANOSEC
+ remuxer->resume = (pthread_cond_t) PTHREAD_COND_INITIALIZER;
+ return remuxer;
+
+error:
+ audiomuxer_free(audio_muxer);
+ avformat_free_context(hls_muxer);
+ free(remuxer);
+ return NULL;
+}
+
+void hls_remuxer_play(HLSRemuxer *remuxer, const char *file)
+{
+ int err;
+ AVPacket *pkt;
+ uint64_t next_second, next_nanosec;
+
+ pkt = av_packet_alloc();
+
+ if (hls_remuxer_init_input(remuxer, file))
+ return;
+
+ int64_t pts;
+ while (1) {
+ /* Demux packet */
+ switch (err = av_read_frame(remuxer->demuxer, pkt)) {
+ case 0:
+ break;
+ default:
+ fprintf(stderr, "Error: %s\n", av_err2str(err));
+ case AVERROR_EOF:
+ fprintf(stderr, "Last packet ts: %f\n", (double)(pts *
+ remuxer->audio_stream->time_base.num) /
+ (remuxer->audio_stream->time_base.den * 60));
+ goto exit;
+ }
+ pts = pkt->pts;
+
+ /* Dispatch packet muxer */
+ if (pkt->stream_index == remuxer->audio_stream_index)
+ err = audiomuxer_send_packet(remuxer->audio_muxer, pkt);
+ else
+ continue;
+
+ if (err) {
+ fprintf(stderr, "Failed to send packet: %s\n", av_err2str(err));
+ break;
+ }
+
+ /* Prevent overrun */
+ remuxer_wait_target_ts(remuxer);
+ }
+
+exit:
+ av_packet_free(&pkt);
+ return;
+}
+
+void hls_remuxer_pause(HLSRemuxer *remuxer)
+{
+ // TODO: mutex?
+ remuxer->state = STATE_PAUSED;
+}
+
+void hls_remuxer_resume(HLSRemuxer *remuxer)
+{
+ pthread_cond_broadcast(&remuxer->resume);
+}
+
+void hls_remuxer_halt(HLSRemuxer *remuxer)
+{
+ // TODO: mutex?
+ remuxer->state = STATE_STANDBY;
+}
+
+void hls_remuxer_free(HLSRemuxer *remuxer)
+{
+ if (remuxer == NULL) return;
+ av_write_trailer(remuxer->hls_writer);
+ audiomuxer_free(remuxer->audio_muxer);
+ free(remuxer);
+}
+
+static int hls_remuxer_init_input(struct HLSRemuxer *remuxer, const char *file)
+{
+ int err;
+ int audio_stream_index;
+ int video_stream_index;
+ const AVStream *audio_stream;
+ const AVStream *video_stream;
+ AVFormatContext *demuxer;
+
+ demuxer = NULL;
+
+ if ((err = avformat_open_input(&demuxer, file, NULL, NULL))) { // TODO: Options
+ fprintf(stderr, "Failed to open '%s': %s\n", file, av_err2str(err));
+ goto error;
+ }
+
+ if ((err = avformat_find_stream_info(demuxer, NULL)) < 0) { // TODO: Options
+ fprintf(stderr, "Failed to find stream info of '%s': %s\n", file, av_err2str(err));
+ goto error;
+ }
+
+ audio_stream_index = av_find_best_stream(demuxer, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
+ video_stream_index = av_find_best_stream(demuxer, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
+
+ audio_stream = audio_stream_index >= 0 ? demuxer->streams[audio_stream_index] : NULL;
+ video_stream = video_stream_index >= 0 ? demuxer->streams[video_stream_index] : NULL;
+ if (audio_stream == NULL && video_stream == NULL) {
+ fprintf(stderr, "Failed to find audio stream index of '%s'\n", file);
+ err = AVERROR_STREAM_NOT_FOUND;
+ goto error;
+ }
+
+ if ((err = audiomuxer_conf(remuxer->audio_muxer, audio_stream)))
+ goto error;
+
+ remuxer->demuxer = demuxer;
+ remuxer->audio_stream = audio_stream;
+ remuxer->video_stream = video_stream;
+ remuxer->audio_stream_index = audio_stream_index;
+ remuxer->video_stream_index = video_stream_index;
+ return 0;
+
+error:
+ avformat_close_input(&demuxer);
+ return err;
+}
+
+static void timespec_add_nano(struct timespec *ts, uint64_t nanoseconds)
+{
+ uint64_t new_nano;
+
+ new_nano = ts->tv_nsec + nanoseconds;
+ ts->tv_nsec = new_nano % NANOSEC;
+ ts->tv_sec += new_nano / NANOSEC;
+}
+
+
+/**
+ * Block the thread if the remuxer is getting close to overruning (writing HLS
+ * segments and discarding previous ones faster than clients can read them).
+ * This is done by checking if all stream[1] have more data ready than needed to
+ * fill the buffer delay (2 seconds by default) and calling clock_nanosleep to
+ * wait off the overrun[2].
+ *
+ * - [1] We only have an audio stream right now.
+ * - [2] TODO: Implement instead of waiting 2 seconds
+ */
+static int remuxer_wait_target_ts (HLSRemuxer *remuxer)
+{
+ uint64_t video_runtime, audio_runtime, min_runtime;
+ struct timespec target_ts;
+
+ audio_runtime = audiomuxer_get_runtime(remuxer->audio_muxer);
+ video_runtime = INT64_MAX;
+ min_runtime = MIN(audio_runtime, video_runtime);
+ assert(min_runtime == audio_runtime);
+ if (min_runtime < remuxer->overhead)
+ return 0;
+ min_runtime -= remuxer->overhead;
+
+ target_ts = remuxer->start_time;
+ timespec_add_nano(&target_ts, min_runtime);
+
+ return clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &target_ts, NULL);
+}
+