#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); }