]> git.sesse.net Git - nageru/blob - player.cpp
Actually send the MJPEG frames on to the HTTP stream.
[nageru] / player.cpp
1 #include <algorithm>
2 #include <chrono>
3 #include <condition_variable>
4 #include <mutex>
5 #include <thread>
6 #include <vector>
7
8 #include <stdio.h>
9
10 extern "C" {
11 #include <libavformat/avformat.h>
12 #include <libavformat/avio.h>
13 }
14
15 #include "clip_list.h"
16 #include "defs.h"
17 #include "ffmpeg_raii.h"
18 #include "httpd.h"
19 #include "jpeg_frame_view.h"
20 #include "mux.h"
21 #include "player.h"
22
23 using namespace std;
24 using namespace std::chrono;
25
26 extern mutex frame_mu;
27 extern vector<int64_t> frames[MAX_STREAMS];
28 extern HTTPD *global_httpd;
29
30 namespace {
31
32 string read_file(const string &filename)
33 {
34         FILE *fp = fopen(filename.c_str(), "rb");
35         if (fp == nullptr) {
36                 perror(filename.c_str());
37                 return "";
38         }
39
40         fseek(fp, 0, SEEK_END);
41         long len = ftell(fp);
42         rewind(fp);
43
44         string ret;
45         ret.resize(len);
46         fread(&ret[0], len, 1, fp);
47         fclose(fp);
48         return ret;
49 }
50
51 }  // namespace
52
53 void Player::thread_func()
54 {
55         for ( ;; ) {
56                 // Wait until we're supposed to play something.
57                 {
58                         unique_lock<mutex> lock(queue_state_mu);
59                         new_clip_changed.wait(lock, [this]{
60                                 return new_clip_ready && current_clip.pts_in != -1;
61                         });
62                         new_clip_ready = false;
63                         playing = true;
64                 }
65
66                 Clip clip;
67                 unsigned stream_idx;
68                 {
69                         lock_guard<mutex> lock(mu);
70                         clip = current_clip;
71                         stream_idx = current_stream_idx;
72                 }
73                 steady_clock::time_point origin = steady_clock::now();
74                 int64_t pts_origin = clip.pts_in;
75
76                 int64_t next_pts = pts_origin - 1;  // Make sure we play the frame at clip.pts_in if it exists.
77
78                 bool aborted = false;
79                 for ( ;; ) {
80                         // Find the next frame.
81                         {
82                                 lock_guard<mutex> lock(frame_mu);
83                                 auto it = upper_bound(frames[stream_idx].begin(),
84                                         frames[stream_idx].end(),
85                                         next_pts);
86                                 if (it == frames[stream_idx].end() || *it >= clip.pts_out) {
87                                         break;
88                                 }
89                                 next_pts = *it;
90                         }
91
92                         // FIXME: assumes a given timebase.
93                         double speed = 0.5;
94                         steady_clock::time_point next_frame_start =
95                                 origin + microseconds((next_pts - pts_origin) * int(1000000 / speed) / 12800);
96
97                         // Sleep until the next frame start, or until there's a new clip we're supposed to play.
98                         {
99                                 unique_lock<mutex> lock(queue_state_mu);
100                                 new_clip_changed.wait_until(lock, next_frame_start, [this]{
101                                         return new_clip_ready || override_stream_idx != -1;
102                                 });
103                                 if (new_clip_ready) break;
104                                 if (override_stream_idx != -1) {
105                                         stream_idx = override_stream_idx;
106                                         override_stream_idx = -1;
107                                         continue;
108                                 }
109                         }
110
111                         destination->setFrame(stream_idx, next_pts);
112
113                         // Send the frame to the stream.
114                         // FIXME: Vaguely less crazy pts, perhaps.
115                         double pts_float = fmod(duration<double>(next_frame_start.time_since_epoch()).count(), 86400.0f);
116                         int64_t pts = lrint(pts_float * TIMEBASE);
117                         string jpeg = read_file(filename_for_frame(stream_idx, next_pts));
118                         AVPacket pkt;
119                         av_init_packet(&pkt);
120                         pkt.stream_index = 0;
121                         pkt.data = (uint8_t *)jpeg.data();
122                         pkt.size = jpeg.size();
123                         stream_mux->add_packet(pkt, pts, pts);
124                 }
125
126                 {
127                         unique_lock<mutex> lock(queue_state_mu);
128                         playing = false;
129                 }
130                 if (done_callback != nullptr && !aborted) {
131                         done_callback();
132                 }
133         }
134 }
135
136 Player::Player(JPEGFrameView *destination)
137         : destination(destination)
138 {
139         open_output_stream();
140         thread(&Player::thread_func, this).detach();
141 }
142
143 void Player::play_clip(const Clip &clip, unsigned stream_idx)
144 {
145         {
146                 lock_guard<mutex> lock(mu);
147                 current_clip = clip;
148                 current_stream_idx = stream_idx;
149         }
150
151         {
152                 lock_guard<mutex> lock(queue_state_mu);
153                 new_clip_ready = true;
154                 override_stream_idx = -1;
155                 new_clip_changed.notify_all();
156         }
157 }
158
159 void Player::override_angle(unsigned stream_idx)
160 {
161         // Corner case: If a new clip is waiting to be played, change its stream and then we're done. 
162         {
163                 unique_lock<mutex> lock(queue_state_mu);
164                 if (new_clip_ready) {
165                         lock_guard<mutex> lock2(mu);
166                         current_stream_idx = stream_idx;
167                         return;
168                 }
169         }
170
171         // If we are playing a clip, set override_stream_idx, and the player thread will
172         // pick it up and change its internal index.
173         {
174                 unique_lock<mutex> lock(queue_state_mu);
175                 if (playing) {
176                         override_stream_idx = stream_idx;
177                         new_clip_changed.notify_all();
178                 }
179         }
180
181         // OK, so we're standing still, presumably at the end of a clip.
182         // Look at the current pts_out (if it exists), and show the closest
183         // thing we've got.
184         int64_t pts_out;
185         {
186                 lock_guard<mutex> lock(mu);
187                 if (current_clip.pts_out < 0) {
188                         return;
189                 }
190                 pts_out = current_clip.pts_out;
191         }
192                         
193         lock_guard<mutex> lock(frame_mu);
194         auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts_out);
195         if (it == frames[stream_idx].end()) {
196                 return;
197         }
198         destination->setFrame(stream_idx, *it);
199 }
200
201 void Player::open_output_stream()
202 {
203         AVFormatContext *avctx = avformat_alloc_context();
204         avctx->oformat = av_guess_format("nut", nullptr, nullptr);
205
206         uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
207         avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, nullptr, nullptr);
208         avctx->pb->write_data_type = &Player::write_packet2_thunk;
209         avctx->pb->ignore_boundary_point = 1;
210
211         Mux::Codec video_codec = Mux::CODEC_MJPEG;
212
213         avctx->flags = AVFMT_FLAG_CUSTOM_IO;
214
215         string video_extradata;
216
217         constexpr int width = 1280, height = 720;  // Doesn't matter for MJPEG.
218         stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, /*audio_codec_parameters=*/nullptr, COARSE_TIMEBASE,
219                 /*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, {}));
220 }
221
222 int Player::write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
223 {
224         Player *player = (Player *)opaque;
225         return player->write_packet2(buf, buf_size, type, time);
226 }
227
228 int Player::write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
229 {
230         if (type == AVIO_DATA_MARKER_SYNC_POINT || type == AVIO_DATA_MARKER_BOUNDARY_POINT) {
231                 seen_sync_markers = true;
232         } else if (type == AVIO_DATA_MARKER_UNKNOWN && !seen_sync_markers) {
233                 // We don't know if this is a keyframe or not (the muxer could
234                 // avoid marking it), so we just have to make the best of it.
235                 type = AVIO_DATA_MARKER_SYNC_POINT;
236         }
237
238         if (type == AVIO_DATA_MARKER_HEADER) {
239                 stream_mux_header.append((char *)buf, buf_size);
240                 global_httpd->set_header(stream_mux_header);
241         } else {
242                 global_httpd->add_data((char *)buf, buf_size, type == AVIO_DATA_MARKER_SYNC_POINT, time, AVRational{ AV_TIME_BASE, 1 });
243         }
244         return buf_size;
245 }