]> git.sesse.net Git - nageru/blob - video_encoder.cpp
Write 1.4.0 changelog.
[nageru] / video_encoder.cpp
1 #include "video_encoder.h"
2
3 #include <assert.h>
4 #include <stdio.h>
5 #include <time.h>
6 #include <unistd.h>
7 #include <string>
8 #include <thread>
9
10 extern "C" {
11 #include <libavutil/mem.h>
12 }
13
14 #include "audio_encoder.h"
15 #include "defs.h"
16 #include "ffmpeg_raii.h"
17 #include "flags.h"
18 #include "httpd.h"
19 #include "mux.h"
20 #include "quicksync_encoder.h"
21 #include "timebase.h"
22 #include "x264_encoder.h"
23
24 class RefCountedFrame;
25
26 using namespace std;
27 using namespace movit;
28
29 namespace {
30
31 string generate_local_dump_filename(int frame)
32 {
33         time_t now = time(NULL);
34         tm now_tm;
35         localtime_r(&now, &now_tm);
36
37         char timestamp[256];
38         strftime(timestamp, sizeof(timestamp), "%F-%T%z", &now_tm);
39
40         // Use the frame number to disambiguate between two cuts starting
41         // on the same second.
42         char filename[256];
43         snprintf(filename, sizeof(filename), "%s%s-f%02d%s",
44                 LOCAL_DUMP_PREFIX, timestamp, frame % 100, LOCAL_DUMP_SUFFIX);
45         return filename;
46 }
47
48 }  // namespace
49
50 VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, HTTPD *httpd, DiskSpaceEstimator *disk_space_estimator)
51         : resource_pool(resource_pool), surface(surface), va_display(va_display), width(width), height(height), httpd(httpd), disk_space_estimator(disk_space_estimator)
52 {
53         oformat = av_guess_format(global_flags.stream_mux_name.c_str(), nullptr, nullptr);
54         assert(oformat != nullptr);
55         if (global_flags.stream_audio_codec_name.empty()) {
56                 stream_audio_encoder.reset(new AudioEncoder(AUDIO_OUTPUT_CODEC_NAME, DEFAULT_AUDIO_OUTPUT_BIT_RATE, oformat));
57         } else {
58                 stream_audio_encoder.reset(new AudioEncoder(global_flags.stream_audio_codec_name, global_flags.stream_audio_codec_bitrate, oformat));
59         }
60         if (global_flags.x264_video_to_http) {
61                 x264_encoder.reset(new X264Encoder(oformat));
62         }
63
64         string filename = generate_local_dump_filename(/*frame=*/0);
65         quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get(), disk_space_estimator));
66
67         open_output_stream();
68         stream_audio_encoder->add_mux(stream_mux.get());
69         quicksync_encoder->set_stream_mux(stream_mux.get());
70         if (global_flags.x264_video_to_http) {
71                 x264_encoder->set_mux(stream_mux.get());
72         }
73 }
74
75 VideoEncoder::~VideoEncoder()
76 {
77         quicksync_encoder.reset(nullptr);
78         while (quicksync_encoders_in_shutdown.load() > 0) {
79                 usleep(10000);
80         }
81 }
82
83 void VideoEncoder::do_cut(int frame)
84 {
85         string filename = generate_local_dump_filename(frame);
86         printf("Starting new recording: %s\n", filename.c_str());
87
88         // Do the shutdown of the old encoder in a separate thread, since it can
89         // take some time (it needs to wait for all the frames in the queue to be
90         // done encoding, for one) and we are running on the main mixer thread.
91         // However, since this means both encoders could be sending packets at
92         // the same time, it means pts could come out of order to the stream mux,
93         // and we need to plug it until the shutdown is complete.
94         stream_mux->plug();
95         lock_guard<mutex> lock(qs_mu);
96         QuickSyncEncoder *old_encoder = quicksync_encoder.release();  // When we go C++14, we can use move capture instead.
97         thread([old_encoder, this]{
98                 old_encoder->shutdown();
99                 stream_mux->unplug();
100
101                 // We cannot delete the encoder here, as this thread has no OpenGL context.
102                 // We'll deal with it in begin_frame().
103                 lock_guard<mutex> lock(qs_mu);
104                 qs_needing_cleanup.emplace_back(old_encoder);
105         }).detach();
106
107         quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get(), disk_space_estimator));
108         quicksync_encoder->set_stream_mux(stream_mux.get());
109 }
110
111 void VideoEncoder::change_x264_bitrate(unsigned rate_kbit)
112 {
113         x264_encoder->change_bitrate(rate_kbit);
114 }
115
116 void VideoEncoder::add_audio(int64_t pts, std::vector<float> audio)
117 {
118         lock_guard<mutex> lock(qs_mu);
119         quicksync_encoder->add_audio(pts, audio);
120         stream_audio_encoder->encode_audio(audio, pts + quicksync_encoder->global_delay());
121 }
122
123 bool VideoEncoder::begin_frame(GLuint *y_tex, GLuint *cbcr_tex)
124 {
125         lock_guard<mutex> lock(qs_mu);
126         qs_needing_cleanup.clear();  // Since we have an OpenGL context here, and are called regularly.
127         return quicksync_encoder->begin_frame(y_tex, cbcr_tex);
128 }
129
130 RefCountedGLsync VideoEncoder::end_frame(int64_t pts, int64_t duration, const std::vector<RefCountedFrame> &input_frames)
131 {
132         lock_guard<mutex> lock(qs_mu);
133         return quicksync_encoder->end_frame(pts, duration, input_frames);
134 }
135
136 void VideoEncoder::open_output_stream()
137 {
138         AVFormatContext *avctx = avformat_alloc_context();
139         avctx->oformat = oformat;
140
141         uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
142         avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, nullptr, nullptr);
143         avctx->pb->write_data_type = &VideoEncoder::write_packet2_thunk;
144         avctx->pb->ignore_boundary_point = 1;
145
146         Mux::Codec video_codec;
147         if (global_flags.uncompressed_video_to_http) {
148                 video_codec = Mux::CODEC_NV12;
149         } else {
150                 video_codec = Mux::CODEC_H264;
151         }
152
153         avctx->flags = AVFMT_FLAG_CUSTOM_IO;
154
155         string video_extradata;
156         if (global_flags.x264_video_to_http) {
157                 video_extradata = x264_encoder->get_global_headers();
158         }
159
160         int time_base = global_flags.stream_coarse_timebase ? COARSE_TIMEBASE : TIMEBASE;
161         stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(), time_base,
162                 /*write_callback=*/nullptr));
163 }
164
165 int VideoEncoder::write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
166 {
167         VideoEncoder *video_encoder = (VideoEncoder *)opaque;
168         return video_encoder->write_packet2(buf, buf_size, type, time);
169 }
170
171 int VideoEncoder::write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
172 {
173         if (type == AVIO_DATA_MARKER_HEADER) {
174                 stream_mux_header.append((char *)buf, buf_size);
175                 httpd->set_header(stream_mux_header);
176         } else {
177                 httpd->add_data((char *)buf, buf_size, type == AVIO_DATA_MARKER_SYNC_POINT);
178         }
179         return buf_size;
180 }
181