#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" }}
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)
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,
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,
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));
}
}
+void MainWindow::cut_triggered()
+{
+ global_mixer->schedule_cut();
+}
+
void MainWindow::cutoff_knob_changed(int value)
{
float octaves = value * 0.1f;
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);
}
}
+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)),
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));
// 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
compressor_enabled = enabled;
}
+ void schedule_cut()
+ {
+ should_cut = true;
+ }
+
void reset_meters();
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;
<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>