]> git.sesse.net Git - nageru/commitdiff
Separate muxing entirely out of the HTTPD class.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 18 Apr 2016 13:24:16 +0000 (15:24 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 18 Apr 2016 13:24:16 +0000 (15:24 +0200)
At some point, it was indeed more convenient to mux separately to everybody,
but muxing needs to know more stuff these days, so it was getting increasingly
ugly. This code follows roughly what VLC does to extract the header and various
block boundaries.

h264encode.cpp
httpd.cpp
httpd.h
mixer.cpp
mux.cpp
mux.h
x264encode.cpp
x264encode.h

index 8c615b4b918ca71b1b020ead900f7a479c8ccd95..b9c52eb55e00914406fc73b131eb47657f13336f 100644 (file)
@@ -42,6 +42,7 @@ extern "C" {
 #include "defs.h"
 #include "flags.h"
 #include "httpd.h"
+#include "mux.h"
 #include "timebase.h"
 #include "x264encode.h"
 
@@ -193,7 +194,7 @@ pair<int64_t, const uint8_t *> FrameReorderer::get_first_frame()
        return make_pair(-pts, storage.second);  // Re-invert pts (see reorder_frame()).
 }
 
-class H264EncoderImpl {
+class H264EncoderImpl : public KeyFrameSignalReceiver {
 public:
        H264EncoderImpl(QSurface *surface, const string &va_display, int width, int height, HTTPD *httpd);
        ~H264EncoderImpl();
@@ -204,6 +205,10 @@ public:
        void open_output_file(const std::string &filename);
        void close_output_file();
 
+       virtual void signal_keyframe() override {
+               stream_mux_writing_keyframes = true;
+       }
+
 private:
        struct storage_task {
                unsigned long long display_order;
@@ -233,13 +238,13 @@ private:
                          int64_t audio_pts,
                          AVCodecContext *ctx,
                          AVAudioResampleContext *resampler,
-                         const vector<PacketDestination *> &destinations);
+                         const vector<Mux *> &muxes);
        void encode_audio_one_frame(const float *audio,
                                    size_t num_samples,  // In each channel.
                                    int64_t audio_pts,
                                    AVCodecContext *ctx,
                                    AVAudioResampleContext *resampler,
-                                   const vector<PacketDestination *> &destinations);
+                                   const vector<Mux *> &muxes);
        void storage_task_enqueue(storage_task task);
        void save_codeddata(storage_task task);
        int render_packedsequence();
@@ -263,6 +268,10 @@ private:
        int release_encode();
        void update_ReferenceFrames(int frame_type);
        int update_RefPicList(int frame_type);
+       void open_output_stream();
+       void close_output_stream();
+       static int write_packet_thunk(void *opaque, uint8_t *buf, int buf_size);
+       int write_packet(uint8_t *buf, int buf_size);
 
        bool is_shutdown = false;
        bool use_zerocopy;
@@ -358,7 +367,15 @@ private:
        int frame_width_mbaligned;
        int frame_height_mbaligned;
 
+       unique_ptr<Mux> stream_mux;  // To HTTP.
        unique_ptr<Mux> file_mux;  // To local disk.
+
+       // While Mux object is constructing, <stream_mux_writing_header> is true,
+       // and the header is being collected into stream_mux_header.
+       bool stream_mux_writing_header;
+       string stream_mux_header;
+
+       bool stream_mux_writing_keyframes = false;
 };
 
 // Supposedly vaRenderPicture() is supposed to destroy the buffer implicitly,
@@ -1653,7 +1670,7 @@ void H264EncoderImpl::save_codeddata(storage_task task)
                }
                if (!global_flags.uncompressed_video_to_http &&
                    !global_flags.x264_video_to_http) {
-                       httpd->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
+                       stream_mux->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
                }
        }
        // Encode and add all audio frames up to and including the pts of this video frame.
@@ -1673,9 +1690,9 @@ void H264EncoderImpl::save_codeddata(storage_task task)
 
                if (context_audio_stream) {
                        encode_audio(audio, &audio_queue_file, audio_pts, context_audio_file, resampler_audio_file, { file_mux.get() });
-                       encode_audio(audio, &audio_queue_stream, audio_pts, context_audio_stream, resampler_audio_stream, { httpd });
+                       encode_audio(audio, &audio_queue_stream, audio_pts, context_audio_stream, resampler_audio_stream, { stream_mux.get() });
                } else {
-                       encode_audio(audio, &audio_queue_file, audio_pts, context_audio_file, resampler_audio_file, { httpd, file_mux.get() });
+                       encode_audio(audio, &audio_queue_file, audio_pts, context_audio_file, resampler_audio_file, { stream_mux.get(), file_mux.get() });
                }
 
                if (audio_pts == task.pts) break;
@@ -1688,13 +1705,13 @@ void H264EncoderImpl::encode_audio(
        int64_t audio_pts,
        AVCodecContext *ctx,
        AVAudioResampleContext *resampler,
-       const vector<PacketDestination *> &destinations)
+       const vector<Mux *> &muxes)
 {
        if (ctx->frame_size == 0) {
                // No queueing needed.
                assert(audio_queue->empty());
                assert(audio.size() % 2 == 0);
-               encode_audio_one_frame(&audio[0], audio.size() / 2, audio_pts, ctx, resampler, destinations);
+               encode_audio_one_frame(&audio[0], audio.size() / 2, audio_pts, ctx, resampler, muxes);
                return;
        }
 
@@ -1711,7 +1728,7 @@ void H264EncoderImpl::encode_audio(
                                       adjusted_audio_pts,
                                       ctx,
                                       resampler,
-                                      destinations);
+                                      muxes);
        }
        audio_queue->erase(audio_queue->begin(), audio_queue->begin() + sample_num);
 }
@@ -1722,7 +1739,7 @@ void H264EncoderImpl::encode_audio_one_frame(
        int64_t audio_pts,
        AVCodecContext *ctx,
        AVAudioResampleContext *resampler,
-       const vector<PacketDestination *> &destinations)
+       const vector<Mux *> &muxes)
 {
        audio_frame->nb_samples = num_samples;
        audio_frame->channel_layout = AV_CH_LAYOUT_STEREO;
@@ -1748,9 +1765,9 @@ void H264EncoderImpl::encode_audio_one_frame(
        avcodec_encode_audio2(ctx, &pkt, audio_frame, &got_output);
        if (got_output) {
                pkt.stream_index = 1;
-               pkt.flags = AV_PKT_FLAG_KEY;
-               for (PacketDestination *dest : destinations) {
-                       dest->add_packet(pkt, audio_pts + global_delay(), audio_pts + global_delay());
+               pkt.flags = 0;
+               for (Mux *mux : muxes) {
+                       mux->add_packet(pkt, audio_pts + global_delay(), audio_pts + global_delay());
                }
        }
 
@@ -1876,7 +1893,7 @@ void init_audio_encoder(const string &codec_name, int bit_rate, AVCodecContext *
 }  // namespace
 
 H264EncoderImpl::H264EncoderImpl(QSurface *surface, const string &va_display, int width, int height, HTTPD *httpd)
-       : current_storage_frame(0), surface(surface), httpd(httpd)
+       : current_storage_frame(0), surface(surface), httpd(httpd), frame_width(width), frame_height(height)
 {
        init_audio_encoder(AUDIO_OUTPUT_CODEC_NAME, DEFAULT_AUDIO_OUTPUT_BIT_RATE, &context_audio_file, &resampler_audio_file);
 
@@ -1885,13 +1902,13 @@ H264EncoderImpl::H264EncoderImpl(QSurface *surface, const string &va_display, in
                        global_flags.stream_audio_codec_bitrate, &context_audio_stream, &resampler_audio_stream);
        }
 
-       audio_frame = av_frame_alloc();
-
-       frame_width = width;
-       frame_height = height;
        frame_width_mbaligned = (frame_width + 15) & (~15);
        frame_height_mbaligned = (frame_height + 15) & (~15);
 
+       open_output_stream();
+
+       audio_frame = av_frame_alloc();
+
        //print_input();
 
        if (global_flags.uncompressed_video_to_http ||
@@ -1899,7 +1916,7 @@ H264EncoderImpl::H264EncoderImpl(QSurface *surface, const string &va_display, in
                reorderer.reset(new FrameReorderer(ip_period - 1, frame_width, frame_height));
        }
        if (global_flags.x264_video_to_http) {
-               x264_encoder.reset(new X264Encoder(httpd));
+               x264_encoder.reset(new X264Encoder(stream_mux.get()));
        }
 
        init_va(va_display);
@@ -1935,6 +1952,7 @@ H264EncoderImpl::~H264EncoderImpl()
        avresample_free(&resampler_audio_stream);
        avcodec_free_context(&context_audio_file);
        avcodec_free_context(&context_audio_stream);
+       close_output_stream();
 }
 
 bool H264EncoderImpl::begin_frame(GLuint *y_tex, GLuint *cbcr_tex)
@@ -2104,7 +2122,7 @@ void H264EncoderImpl::open_output_file(const std::string &filename)
                exit(1);
        }
 
-       file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, context_audio_file->codec, TIMEBASE, DEFAULT_AUDIO_OUTPUT_BIT_RATE));
+       file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, context_audio_file->codec, TIMEBASE, DEFAULT_AUDIO_OUTPUT_BIT_RATE, nullptr));
 }
 
 void H264EncoderImpl::close_output_file()
@@ -2112,6 +2130,71 @@ void H264EncoderImpl::close_output_file()
         file_mux.reset();
 }
 
+void H264EncoderImpl::open_output_stream()
+{
+       AVFormatContext *avctx = avformat_alloc_context();
+       AVOutputFormat *oformat = av_guess_format(global_flags.stream_mux_name.c_str(), nullptr, nullptr);
+       assert(oformat != nullptr);
+       avctx->oformat = oformat;
+
+       string codec_name;
+       int bit_rate;
+
+       if (global_flags.stream_audio_codec_name.empty()) {
+               codec_name = AUDIO_OUTPUT_CODEC_NAME;
+               bit_rate = DEFAULT_AUDIO_OUTPUT_BIT_RATE;
+       } else {
+               codec_name = global_flags.stream_audio_codec_name;
+               bit_rate = global_flags.stream_audio_codec_bitrate;
+       }
+
+       uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
+       avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, &H264EncoderImpl::write_packet_thunk, nullptr);
+
+       Mux::Codec video_codec;
+       if (global_flags.uncompressed_video_to_http) {
+               video_codec = Mux::CODEC_NV12;
+       } else {
+               video_codec = Mux::CODEC_H264;
+       }
+
+       avctx->flags = AVFMT_FLAG_CUSTOM_IO;
+       AVCodec *codec_audio = avcodec_find_encoder_by_name(codec_name.c_str());
+       if (codec_audio == nullptr) {
+               fprintf(stderr, "ERROR: Could not find codec '%s'\n", codec_name.c_str());
+               exit(1);
+       }
+
+       int time_base = global_flags.stream_coarse_timebase ? COARSE_TIMEBASE : TIMEBASE;
+       stream_mux_writing_header = true;
+       stream_mux.reset(new Mux(avctx, frame_width, frame_height, video_codec, codec_audio, time_base, bit_rate, this));
+       stream_mux_writing_header = false;
+       httpd->set_header(stream_mux_header);
+       stream_mux_header.clear();
+}
+
+void H264EncoderImpl::close_output_stream()
+{
+       stream_mux.reset();
+}
+
+int H264EncoderImpl::write_packet_thunk(void *opaque, uint8_t *buf, int buf_size)
+{
+       H264EncoderImpl *h264_encoder = (H264EncoderImpl *)opaque;
+       return h264_encoder->write_packet(buf, buf_size);
+}
+
+int H264EncoderImpl::write_packet(uint8_t *buf, int buf_size)
+{
+       if (stream_mux_writing_header) {
+               stream_mux_header.append((char *)buf, buf_size);
+       } else {
+               httpd->add_data((char *)buf, buf_size, stream_mux_writing_keyframes);
+               stream_mux_writing_keyframes = false;
+       }
+       return buf_size;
+}
+
 void H264EncoderImpl::encode_thread_func()
 {
        int64_t last_dts = -1;
@@ -2201,7 +2284,7 @@ void H264EncoderImpl::add_packet_for_uncompressed_frame(int64_t pts, const uint8
        pkt.size = frame_width * frame_height * 2;
        pkt.stream_index = 0;
        pkt.flags = AV_PKT_FLAG_KEY;
-       httpd->add_packet(pkt, pts, pts);
+       stream_mux->add_packet(pkt, pts, pts);
 }
 
 namespace {
index 5900e0ab1425abce934571b8013d4252566d5efc..ed46f52566832d6faa1ac63e0edfe071d8030e6b 100644 (file)
--- a/httpd.cpp
+++ b/httpd.cpp
@@ -4,16 +4,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavutil/channel_layout.h>
-#include <libavutil/mathematics.h>
-#include <libavutil/mem.h>
-#include <libavutil/pixfmt.h>
-#include <libavutil/rational.h>
-#include <libavutil/samplefmt.h>
-}
-
 #include <vector>
 
 #include "httpd.h"
@@ -27,8 +17,7 @@ struct MHD_Response;
 
 using namespace std;
 
-HTTPD::HTTPD(int width, int height)
-       : width(width), height(height)
+HTTPD::HTTPD()
 {
 }
 
@@ -42,11 +31,11 @@ void HTTPD::start(int port)
                         MHD_OPTION_END);
 }
 
-void HTTPD::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
+void HTTPD::add_data(const char *buf, size_t size, bool keyframe)
 {
        unique_lock<mutex> lock(streams_mutex);
        for (Stream *stream : streams) {
-               stream->add_packet(pkt, pts, dts);
+               stream->add_data(buf, size, keyframe ? Stream::DATA_TYPE_KEYFRAME : Stream::DATA_TYPE_OTHER);
        }
 }
 
@@ -64,16 +53,8 @@ int HTTPD::answer_to_connection(MHD_Connection *connection,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **con_cls)
 {
-       AVOutputFormat *oformat = av_guess_format(global_flags.stream_mux_name.c_str(), nullptr, nullptr);
-       assert(oformat != nullptr);
-
-       // TODO: This is an ugly place to have this logic.
-       const int bit_rate = global_flags.stream_audio_codec_name.empty() ?
-               DEFAULT_AUDIO_OUTPUT_BIT_RATE :
-               global_flags.stream_audio_codec_bitrate;
-
-       int time_base = global_flags.stream_coarse_timebase ? COARSE_TIMEBASE : TIMEBASE;
-       HTTPD::Stream *stream = new HTTPD::Stream(oformat, width, height, time_base, bit_rate);
+       HTTPD::Stream *stream = new HTTPD::Stream;
+       stream->add_data(header.data(), header.size(), Stream::DATA_TYPE_HEADER);
        {
                unique_lock<mutex> lock(streams_mutex);
                streams.insert(stream);
@@ -117,36 +98,6 @@ void HTTPD::request_completed(struct MHD_Connection *connection, void **con_cls,
        }
 }
 
-HTTPD::Stream::Stream(AVOutputFormat *oformat, int width, int height, int time_base, int bit_rate)
-{
-       AVFormatContext *avctx = avformat_alloc_context();
-       avctx->oformat = oformat;
-       uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
-       avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, &HTTPD::Stream::write_packet_thunk, nullptr);
-
-       Mux::Codec video_codec;
-       if (global_flags.uncompressed_video_to_http) {
-               video_codec = Mux::CODEC_NV12;
-       } else {
-               video_codec = Mux::CODEC_H264;
-       }
-
-       avctx->flags = AVFMT_FLAG_CUSTOM_IO;
-
-       // TODO: This is an ugly place to have this logic.
-       const string codec_name = global_flags.stream_audio_codec_name.empty() ?
-               AUDIO_OUTPUT_CODEC_NAME :
-               global_flags.stream_audio_codec_name;
-
-       AVCodec *codec_audio = avcodec_find_encoder_by_name(codec_name.c_str());
-       if (codec_audio == nullptr) {
-               fprintf(stderr, "ERROR: Could not find codec '%s'\n", codec_name.c_str());
-               exit(1);
-       }
-
-       mux.reset(new Mux(avctx, width, height, video_codec, codec_audio, time_base, bit_rate));
-}
-
 ssize_t HTTPD::Stream::reader_callback_thunk(void *cls, uint64_t pos, char *buf, size_t max)
 {
        HTTPD::Stream *stream = (HTTPD::Stream *)cls;
@@ -184,22 +135,20 @@ ssize_t HTTPD::Stream::reader_callback(uint64_t pos, char *buf, size_t max)
        return ret;
 }
 
-void HTTPD::Stream::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
-{
-       mux->add_packet(pkt, pts, dts);
-}
-
-int HTTPD::Stream::write_packet_thunk(void *opaque, uint8_t *buf, int buf_size)
+void HTTPD::Stream::add_data(const char *buf, size_t buf_size, HTTPD::Stream::DataType data_type)
 {
-       HTTPD::Stream *stream = (HTTPD::Stream *)opaque;
-       return stream->write_packet(buf, buf_size);
-}
+       if (buf_size == 0) {
+               return;
+       }
+       if (data_type == DATA_TYPE_KEYFRAME) {
+               seen_keyframe = true;
+       } else if (data_type == DATA_TYPE_OTHER && !seen_keyframe) {
+               // Start sending only once we see a keyframe.
+               return;
+       }
 
-int HTTPD::Stream::write_packet(uint8_t *buf, int buf_size)
-{
        unique_lock<mutex> lock(buffer_mutex);
-       buffered_data.emplace_back((char *)buf, buf_size);
+       buffered_data.emplace_back(buf, buf_size);
        has_buffered_data.notify_all(); 
-       return buf_size;
 }
 
diff --git a/httpd.h b/httpd.h
index 11f15bd81fd9314433edf72e4da19d6e6e2bcb39..21d8746172adc5231820a8c6a676d6bf08ef8ff0 100644 (file)
--- a/httpd.h
+++ b/httpd.h
@@ -1,11 +1,7 @@
 #ifndef _HTTPD_H
 #define _HTTPD_H
 
-// A class dealing with stream output to HTTP. Since we generally have very few outputs
-// (end clients are not meant to connect directly to our stream; it should be
-// transcoded by something else and then sent to a reflector), we don't need to
-// care a lot about performance. Thus, we solve this by the simplest possible
-// way, namely having one ffmpeg mux per output.
+// A class dealing with stream output to HTTP.
 
 #include <microhttpd.h>
 #include <stddef.h>
 
 struct MHD_Connection;
 
-extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libavformat/avio.h>
-}
+class HTTPD {
+public:
+       HTTPD();
 
-#include "mux.h"
+       // Should be called before start().
+       void set_header(const std::string &data) {
+               header = data;
+       }
 
-class HTTPD : public PacketDestination {
-public:
-       HTTPD(int width, int height);
        void start(int port);
-       void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts) override;
+       void add_data(const char *buf, size_t size, bool keyframe);
 
 private:
        static int answer_to_connection_thunk(void *cls, MHD_Connection *connection,
@@ -54,29 +48,27 @@ private:
 
        class Stream {
        public:
-               Stream(AVOutputFormat *oformat, int width, int height, int time_base, int bit_rate);
-
                static ssize_t reader_callback_thunk(void *cls, uint64_t pos, char *buf, size_t max);
                ssize_t reader_callback(uint64_t pos, char *buf, size_t max);
 
-               void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts);
+               enum DataType {
+                       DATA_TYPE_HEADER,
+                       DATA_TYPE_KEYFRAME,
+                       DATA_TYPE_OTHER
+               };
+               void add_data(const char *buf, size_t size, DataType data_type);
 
        private:
-               static int write_packet_thunk(void *opaque, uint8_t *buf, int buf_size);
-               int write_packet(uint8_t *buf, int buf_size);
-
                std::mutex buffer_mutex;
                std::condition_variable has_buffered_data;
                std::deque<std::string> buffered_data;  // Protected by <mutex>.
                size_t used_of_buffered_data = 0;  // How many bytes of the first element of <buffered_data> that is already used. Protected by <mutex>.
-
-               std::unique_ptr<Mux> mux;  // Must come last to be destroyed before buffered_data, since the destructor can write bytes.
+               size_t seen_keyframe = false;
        };
 
        std::mutex streams_mutex;
        std::set<Stream *> streams;  // Not owned.
-
-       int width, height;
+       std::string header;
 };
 
 #endif  // !defined(_HTTPD_H)
index 3dc8a4c3e5c3630965106755c38f6e4a1373c865..8c95f386f5f4d94d929d4496be31b6a3f6950a9a 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -139,7 +139,7 @@ void QueueLengthPolicy::update_policy(int queue_length)
 }
 
 Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
-       : httpd(WIDTH, HEIGHT),
+       : httpd(),
          num_cards(num_cards),
          mixer_surface(create_surface(format)),
          h264_encoder_surface(create_surface(format)),
@@ -148,8 +148,6 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
          limiter(OUTPUT_FREQUENCY),
          compressor(OUTPUT_FREQUENCY)
 {
-       httpd.start(9095);
-
        CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF));
        check_error();
 
@@ -179,6 +177,9 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        h264_encoder.reset(new H264Encoder(h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd));
        h264_encoder->open_output_file(generate_local_dump_filename(/*frame=*/0).c_str());
 
+       // Start listening for clients only once H264Encoder has written its header, if any.
+       httpd.start(9095);
+
        // First try initializing the PCI devices, then USB, until we have the desired number of cards.
        unsigned num_pci_devices = 0, num_usb_devices = 0;
        unsigned card_index = 0;
diff --git a/mux.cpp b/mux.cpp
index addd24c7c8c739ced966f5ad51dba0b6d98c7fca..eff336f5799d01706fd7f9c4a2a0fc99356303f6 100644 (file)
--- a/mux.cpp
+++ b/mux.cpp
@@ -9,8 +9,8 @@
 
 using namespace std;
 
-Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const AVCodec *codec_audio, int time_base, int bit_rate)
-       : avctx(avctx)
+Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const AVCodec *codec_audio, int time_base, int bit_rate, KeyFrameSignalReceiver *keyframe_signal_receiver)
+       : avctx(avctx), keyframe_signal_receiver(keyframe_signal_receiver)
 {
        AVCodec *codec_video = avcodec_find_encoder((video_codec == CODEC_H264) ? AV_CODEC_ID_H264 : AV_CODEC_ID_RAWVIDEO);
        avstream_video = avformat_new_stream(avctx, codec_video);
@@ -70,6 +70,9 @@ Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const
                fprintf(stderr, "avformat_write_header() failed\n");
                exit(1);
        }
+
+       // Make sure the header is written before the constructor exits.
+       avio_flush(avctx->pb);
 }
 
 Mux::~Mux()
@@ -82,12 +85,6 @@ Mux::~Mux()
 
 void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
 {
-       if (!seen_keyframe && !(pkt.stream_index == 0 && (pkt.flags & AV_PKT_FLAG_KEY))) {
-               // Wait until we see the first (video) key frame.
-               return;
-       }
-       seen_keyframe = true;
-
        AVPacket pkt_copy;
        av_copy_packet(&pkt_copy, &pkt);
        if (pkt.stream_index == 0) {
@@ -100,6 +97,15 @@ void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
                assert(false);
        }
 
+       if (keyframe_signal_receiver) {
+               if (pkt.flags & AV_PKT_FLAG_KEY) {
+                       if (avctx->oformat->flags & AVFMT_ALLOW_FLUSH) {
+                               av_write_frame(avctx, nullptr);
+                       }
+                       keyframe_signal_receiver->signal_keyframe();
+               }
+       }
+
        if (av_interleaved_write_frame(avctx, &pkt_copy) < 0) {
                fprintf(stderr, "av_interleaved_write_frame() failed\n");
                exit(1);
@@ -107,4 +113,3 @@ void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
 
        av_packet_unref(&pkt_copy);
 }
-
diff --git a/mux.h b/mux.h
index 47ff775d8cdf2d0faa2148ae1b55100d2bbcd8b1..2eb8eeaa45f1472f471488736b9638ce382b2407 100644 (file)
--- a/mux.h
+++ b/mux.h
@@ -9,28 +9,28 @@ extern "C" {
 #include <libavformat/avio.h>
 }
 
-class PacketDestination {
+class KeyFrameSignalReceiver {
 public:
-       virtual ~PacketDestination() {}
-       virtual void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts) = 0;
+       // Needs to automatically turn the flag off again after actually receiving data.
+       virtual void signal_keyframe() = 0;
 };
 
-class Mux : public PacketDestination {
+class Mux {
 public:
        enum Codec {
                CODEC_H264,
                CODEC_NV12,  // Uncompressed 4:2:0.
        };
 
-       // Takes ownership of avctx.
-       Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const AVCodec *codec_audio, int time_base, int bit_rate);
+       // Takes ownership of avctx. <keyframe_signal_receiver> can be nullptr.
+       Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const AVCodec *codec_audio, int time_base, int bit_rate, KeyFrameSignalReceiver *keyframe_signal_receiver);
        ~Mux();
-       void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts) override;
+       void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts);
 
 private:
-       bool seen_keyframe = false;
        AVFormatContext *avctx;
        AVStream *avstream_video, *avstream_audio;
+       KeyFrameSignalReceiver *keyframe_signal_receiver;
 };
 
 #endif  // !defined(_MUX_H)
index 4efa3774489d5e564ebbbf8f9f8df79930a19fbd..a72d940f8a115eb8bde0be93fb7adb953b02d7e1 100644 (file)
@@ -1,7 +1,8 @@
 #include <string.h>
+#include <unistd.h>
 
 #include "defs.h"
-#include "httpd.h"
+#include "mux.h"
 #include "timebase.h"
 #include "x264encode.h"
 
@@ -11,8 +12,8 @@ extern "C" {
 
 using namespace std;
 
-X264Encoder::X264Encoder(HTTPD *httpd)
-       : httpd(httpd)
+X264Encoder::X264Encoder(Mux *mux)
+       : mux(mux)
 {
        frame_pool.reset(new uint8_t[WIDTH * HEIGHT * 2 * X264_QUEUE_LENGTH]);
        for (unsigned i = 0; i < X264_QUEUE_LENGTH; ++i) {
@@ -166,5 +167,5 @@ void X264Encoder::encode_frame(X264Encoder::QueuedFrame qf)
                pkt.flags = 0;
        }
 
-       httpd->add_packet(pkt, pic.i_pts, pic.i_dts);
+       mux->add_packet(pkt, pic.i_pts, pic.i_dts);
 }      
index f1c3a37380beff14e8fa1c8d90b55be09f98363f..83c5e8be88d83729706a102bec8b893e14176e9b 100644 (file)
@@ -28,11 +28,11 @@ extern "C" {
 #include "x264.h"
 }
 
-class HTTPD;
+class Mux;
 
 class X264Encoder {
 public:
-       X264Encoder(HTTPD *httpd);  // Does not take ownership.
+       X264Encoder(Mux *httpd);  // Does not take ownership.
        ~X264Encoder();
 
        // <data> is taken to be raw NV12 data of WIDTHxHEIGHT resolution.
@@ -57,7 +57,7 @@ private:
        // pool.
        std::unique_ptr<uint8_t[]> frame_pool;
 
-       HTTPD *httpd = nullptr;
+       Mux *mux = nullptr;
 
        std::thread encoder_thread;
        x264_t *x264;