]> git.sesse.net Git - nageru/blob - video_stream.cpp
Move stream generation into a new class VideoStream, which will also soon deal with...
[nageru] / video_stream.cpp
1 #include "video_stream.h"
2
3 extern "C" {
4 #include <libavformat/avformat.h>
5 #include <libavformat/avio.h>
6 }
7
8 #include "httpd.h"
9 #include "jpeg_frame_view.h"
10 #include "mux.h"
11 #include "player.h"
12
13 using namespace std;
14
15 extern HTTPD *global_httpd;
16
17 namespace {
18
19 string read_file(const string &filename)
20 {
21         FILE *fp = fopen(filename.c_str(), "rb");
22         if (fp == nullptr) {
23                 perror(filename.c_str());
24                 return "";
25         }
26
27         fseek(fp, 0, SEEK_END);
28         long len = ftell(fp);
29         rewind(fp);
30
31         string ret;
32         ret.resize(len);
33         fread(&ret[0], len, 1, fp);
34         fclose(fp);
35         return ret;
36 }
37
38 }  // namespace
39
40 void VideoStream::start()
41 {
42         AVFormatContext *avctx = avformat_alloc_context();
43         avctx->oformat = av_guess_format("nut", nullptr, nullptr);
44
45         uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
46         avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, nullptr, nullptr);
47         avctx->pb->write_data_type = &VideoStream::write_packet2_thunk;
48         avctx->pb->ignore_boundary_point = 1;
49
50         Mux::Codec video_codec = Mux::CODEC_MJPEG;
51
52         avctx->flags = AVFMT_FLAG_CUSTOM_IO;
53
54         string video_extradata;
55
56         constexpr int width = 1280, height = 720;  // Doesn't matter for MJPEG.
57         stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, /*audio_codec_parameters=*/nullptr, COARSE_TIMEBASE,
58                 /*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, {}));
59
60
61         encode_thread = thread(&VideoStream::encode_thread_func, this);
62 }
63
64 void VideoStream::stop()
65 {
66         encode_thread.join();
67 }
68
69 void VideoStream::schedule_original_frame(int64_t output_pts, unsigned stream_idx, int64_t input_pts)
70 {
71         unique_lock<mutex> lock(queue_lock);
72         QueuedFrame qf;
73         qf.output_pts = output_pts;
74         qf.stream_idx = stream_idx;
75         qf.input_first_pts = input_pts; 
76         frame_queue.push_back(qf);
77         queue_nonempty.notify_all();
78 }
79
80 void VideoStream::encode_thread_func()
81 {
82         for ( ;; ) {
83                 QueuedFrame qf;
84                 {
85                         unique_lock<mutex> lock(queue_lock);
86                         queue_nonempty.wait(lock, [this]{
87                                 return !frame_queue.empty();
88                         });
89                         qf = frame_queue.front();
90                         frame_queue.pop_front();
91                 }
92
93                 if (qf.type == QueuedFrame::ORIGINAL) {
94                         string jpeg = read_file(filename_for_frame(qf.stream_idx, qf.input_first_pts));
95                         AVPacket pkt;
96                         av_init_packet(&pkt);
97                         pkt.stream_index = 0;
98                         pkt.data = (uint8_t *)jpeg.data();
99                         pkt.size = jpeg.size();
100                         stream_mux->add_packet(pkt, qf.output_pts, qf.output_pts);
101                 }               
102         }
103 }
104
105 int VideoStream::write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
106 {
107         VideoStream *video_stream = (VideoStream *)opaque;
108         return video_stream->write_packet2(buf, buf_size, type, time);
109 }
110
111 int VideoStream::write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
112 {
113         if (type == AVIO_DATA_MARKER_SYNC_POINT || type == AVIO_DATA_MARKER_BOUNDARY_POINT) {
114                 seen_sync_markers = true;
115         } else if (type == AVIO_DATA_MARKER_UNKNOWN && !seen_sync_markers) {
116                 // We don't know if this is a keyframe or not (the muxer could
117                 // avoid marking it), so we just have to make the best of it.
118                 type = AVIO_DATA_MARKER_SYNC_POINT;
119         }
120
121         if (type == AVIO_DATA_MARKER_HEADER) {
122                 stream_mux_header.append((char *)buf, buf_size);
123                 global_httpd->set_header(stream_mux_header);
124         } else {
125                 global_httpd->add_data((char *)buf, buf_size, type == AVIO_DATA_MARKER_SYNC_POINT, time, AVRational{ AV_TIME_BASE, 1 });
126         }
127         return buf_size;
128 }
129