From c1cb78d574c0429aa5e3ff3a2b3886e4bc153212 Mon Sep 17 00:00:00 2001 From: bbergeron Date: Wed, 3 Apr 2024 17:32:01 -0400 Subject: 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). --- transmuxer.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 transmuxer.c (limited to 'transmuxer.c') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} + -- cgit v1.2.3