]> git.sesse.net Git - nageru/commitdiff
Add a menu item to start a new video segment (cut/cue, except that would be a confusi...
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 10 Jan 2016 19:57:04 +0000 (20:57 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 10 Jan 2016 19:57:04 +0000 (20:57 +0100)
defs.h
httpd.cpp
httpd.h
mainwindow.cpp
mainwindow.h
mixer.cpp
mixer.h
ui_mainwindow.ui

diff --git a/defs.h b/defs.h
index ef5aefd715179bebedcfacabcd2c6121a4d18600..9a08bc37d591a90817462b4a35f3c3b17b72195f 100644 (file)
--- a/defs.h
+++ b/defs.h
@@ -14,7 +14,8 @@
 #define AUDIO_OUTPUT_SAMPLE_FMT AV_SAMPLE_FMT_S32
 #define AUDIO_OUTPUT_BIT_RATE 0
 
-#define LOCAL_DUMP_FILE_NAME "test.mov"
+#define LOCAL_DUMP_PREFIX "record-"
+#define LOCAL_DUMP_SUFFIX ".mov"
 #define STREAM_MUX_NAME "mov"
 #define MUX_OPTS {{ "movflags", "empty_moov+frag_keyframe+default_base_moof" }}
 
index f148fa1d22b889c2a64d8576a34e2ee8cce118de..29923f9430413ee5dec3175e0aaae42a7f257cf1 100644 (file)
--- a/httpd.cpp
+++ b/httpd.cpp
@@ -24,20 +24,9 @@ struct MHD_Response;
 
 using namespace std;
 
-HTTPD::HTTPD(const char *output_filename, int width, int height)
+HTTPD::HTTPD(int width, int height)
        : width(width), height(height)
 {
-       AVFormatContext *avctx = avformat_alloc_context();
-       avctx->oformat = av_guess_format(NULL, output_filename, NULL);
-       strcpy(avctx->filename, output_filename);
-       int ret = avio_open2(&avctx->pb, output_filename, AVIO_FLAG_WRITE, &avctx->interrupt_callback, NULL);
-       if (ret < 0) {
-               char tmp[AV_ERROR_MAX_STRING_SIZE];
-               fprintf(stderr, "%s: avio_open2() failed: %s\n", output_filename, av_make_error_string(tmp, sizeof(tmp), ret));
-               exit(1);
-       }
-
-       file_mux.reset(new Mux(avctx, width, height));
 }
 
 void HTTPD::start(int port)
@@ -53,7 +42,31 @@ void HTTPD::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
        for (Stream *stream : streams) {
                stream->add_packet(pkt, pts, dts);
        }
-       file_mux->add_packet(pkt, pts, dts);
+       if (file_mux) {
+               file_mux->add_packet(pkt, pts, dts);
+       }
+}
+
+void HTTPD::open_output_file(const string &filename)
+{
+       AVFormatContext *avctx = avformat_alloc_context();
+       avctx->oformat = av_guess_format(NULL, filename.c_str(), NULL);
+       strcpy(avctx->filename, filename.c_str());
+
+       string url = "file:" + filename;
+       int ret = avio_open2(&avctx->pb, url.c_str(), AVIO_FLAG_WRITE, &avctx->interrupt_callback, NULL);
+       if (ret < 0) {
+               char tmp[AV_ERROR_MAX_STRING_SIZE];
+               fprintf(stderr, "%s: avio_open2() failed: %s\n", filename.c_str(), av_make_error_string(tmp, sizeof(tmp), ret));
+               exit(1);
+       }
+
+       file_mux.reset(new Mux(avctx, width, height));
+}
+
+void HTTPD::close_output_file()
+{
+       file_mux.reset();
 }
 
 int HTTPD::answer_to_connection_thunk(void *cls, MHD_Connection *connection,
diff --git a/httpd.h b/httpd.h
index d546feec22180ab4c9bf7fe10be417d9f7b6dff5..e8efc030b4706d92b64e89e7ea748c056d8f7c85 100644 (file)
--- a/httpd.h
+++ b/httpd.h
@@ -29,10 +29,14 @@ extern "C" {
 
 class HTTPD {
 public:
-       HTTPD(const char *output_filename, int width, int height);
+       HTTPD(int width, int height);
        void start(int port);
        void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts);
 
+       // You can only have one going at the same time.
+       void open_output_file(const std::string &filename);
+       void close_output_file();
+
 private:
        static int answer_to_connection_thunk(void *cls, MHD_Connection *connection,
                                              const char *url, const char *method,
index f15e56ae008e5c5058e7f39807d21f05cf073670..e23d191d978511589e1e3ab8ec146232812a8d81 100644 (file)
@@ -41,6 +41,9 @@ MainWindow::MainWindow()
        ui->me_live->set_output(Mixer::OUTPUT_LIVE);
        ui->me_preview->set_output(Mixer::OUTPUT_PREVIEW);
 
+       // The menu.
+       connect(ui->cut_action, &QAction::triggered, this, &MainWindow::cut_triggered);
+
        // Hook up the transition buttons.
        // TODO: Make them dynamic.
        connect(ui->transition_btn1, &QPushButton::clicked, bind(&MainWindow::transition_clicked, this, 0));
@@ -126,6 +129,11 @@ void MainWindow::mixer_shutting_down()
        }
 }
 
+void MainWindow::cut_triggered()
+{
+       global_mixer->schedule_cut();
+}
+
 void MainWindow::cutoff_knob_changed(int value)
 {
        float octaves = value * 0.1f;
index f768aaa31445ecb9bf92a54f314586d892b78876..bb5a8707734433a0b79b4363e8a563033c1050d2 100644 (file)
@@ -32,6 +32,7 @@ public:
        void mixer_shutting_down();
 
 public slots:
+       void cut_triggered();
        void transition_clicked(int transition_number);
        void channel_clicked(int channel_number);
        void wb_button_clicked(int channel_number);
index 1a4a3f9d3c292f4d4aaa85a4415c8420bc1083b7..6e1e45d953762ba55523248ea9a38345fe669c23 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -74,10 +74,27 @@ void insert_new_frame(RefCountedFrame frame, unsigned field_num, bool interlaced
        }
 }
 
+string generate_local_dump_filename(int frame)
+{
+       time_t now = time(NULL);
+       tm now_tm;
+       localtime_r(&now, &now_tm);
+
+       char timestamp[256];
+       strftime(timestamp, sizeof(timestamp), "%F-%T%z", &now_tm);
+
+       // Use the frame number to disambiguate between two cuts starting
+       // on the same second.
+       char filename[256];
+       snprintf(filename, sizeof(filename), "%s%s-f%02d%s",
+               LOCAL_DUMP_PREFIX, timestamp, frame % 100, LOCAL_DUMP_SUFFIX);
+       return filename;
+}
+
 }  // namespace
 
 Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
-       : httpd(LOCAL_DUMP_FILE_NAME, WIDTH, HEIGHT),
+       : httpd(WIDTH, HEIGHT),
          num_cards(num_cards),
          mixer_surface(create_surface(format)),
          h264_encoder_surface(create_surface(format)),
@@ -85,6 +102,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
          limiter(OUTPUT_FREQUENCY),
          compressor(OUTPUT_FREQUENCY)
 {
+       httpd.open_output_file(generate_local_dump_filename(/*frame=*/0).c_str());
        httpd.start(9095);
 
        CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF));
@@ -626,6 +644,15 @@ void Mixer::thread_func()
                //      chain->print_phase_timing();
                }
 
+               if (should_cut.exchange(false)) {  // Test and clear.
+                       string filename = generate_local_dump_filename(frame);
+                       printf("Starting new recording: %s\n", filename.c_str());
+                       h264_encoder->shutdown();
+                       httpd.close_output_file();
+                       httpd.open_output_file(filename.c_str());
+                       h264_encoder.reset(new H264Encoder(h264_encoder_surface, WIDTH, HEIGHT, &httpd));
+               }
+
 #if 0
                // Reset every 100 frames, so that local variations in frame times
                // (especially for the first few frames, when the shaders are
diff --git a/mixer.h b/mixer.h
index 6d0cbce1593b6da92c4f426668d948c982683921..db6db15155bacb85fd0fe011296cf770422039f5 100644 (file)
--- a/mixer.h
+++ b/mixer.h
@@ -169,6 +169,11 @@ public:
                compressor_enabled = enabled;
        }
 
+       void schedule_cut()
+       {
+               should_cut = true;
+       }
+
        void reset_meters();
 
 private:
@@ -252,6 +257,7 @@ private:
        std::thread mixer_thread;
        std::thread audio_thread;
        std::atomic<bool> should_quit{false};
+       std::atomic<bool> should_cut{false};
 
        audio_level_callback_t audio_level_callback = nullptr;
        std::mutex r128_mutex;
index 62a35f61260488a884de7c46b99466a9ef7d7890..1912efbe7971aad7bf5cea41af30d0539de1fe4d 100644 (file)
     <property name="title">
      <string>Video</string>
     </property>
+    <addaction name="cut_action"/>
     <addaction name="exit_action"/>
    </widget>
    <addaction name="menuWhat"/>
     <string>Exit</string>
    </property>
   </action>
+  <action name="cut_action">
+   <property name="text">
+    <string>Begin new video segment</string>
+   </property>
+  </action>
  </widget>
  <layoutdefault spacing="6" margin="11"/>
  <customwidgets>