Instrument Neutral Distributed Interface INDI 2.2.5
theorarecorder.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 by Jasem Mutlaq <[email protected]>
3
4 Theora Recorder
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20*/
21
22#include "theorarecorder.h"
23#include "jpegutils.h"
24#include "ccvt.h"
25
26#define _FILE_OFFSET_BITS 64
27
28#include <ctime>
29#include <cerrno>
30#include <cstring>
31#include <sys/time.h>
32
33#define ERRMSGSIZ 1024
34
35static int ilog(unsigned _v)
36{
37 int ret;
38 for(ret = 0; _v; ret++)_v >>= 1;
39 return ret;
40}
41
42namespace INDI
43{
44
46{
47 name = "OGV";
48 isRecordingActive = false;
49
50 ycbcr[0].data = nullptr;
51 ycbcr[1].data = nullptr;
52 ycbcr[2].data = nullptr;
53}
54
56{
57 delete [] ycbcr[0].data;
58 delete [] ycbcr[1].data;
59 delete [] ycbcr[2].data;
60
61 th_encode_free(td);
62}
63
64bool TheoraRecorder::setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth)
65{
66 m_PixelFormat = pixelFormat;
67 m_PixelDepth = pixelDepth;
68 return true;
69}
70
71bool TheoraRecorder::setSize(uint16_t width, uint16_t height)
72{
74 return false;
75
76 rawWidth = width;
77 rawHeight = height;
78
79 return allocateBuffers();
80}
81
82bool TheoraRecorder::allocateBuffers()
83{
84 /* Must hold: yuv_w >= w */
85 uint16_t yuv_w = (rawWidth + 15) & ~15;
86 /* Must hold: yuv_h >= h */
87 uint16_t yuv_h = (rawHeight + 15) & ~15;
88
89 /* Do we need to allocate a buffer */
90 if (!ycbcr[0].data || yuv_w != ycbcr[0].width || yuv_h != ycbcr[0].height)
91 {
92 ycbcr[0].width = yuv_w;
93 ycbcr[0].height = yuv_h;
94 ycbcr[0].stride = yuv_w;
95#if 0
97 {
98 ycbcr[1].width = 0;
99 ycbcr[1].stride = 0;
100 ycbcr[1].height = 0;
101 ycbcr[2].width = 0;
102 ycbcr[2].stride = 0;
103 ycbcr[2].height = 0;
104 }
105 else
106 {
107#endif
108 ycbcr[1].width = (chroma_format == TH_PF_444) ? yuv_w : (yuv_w >> 1);
109 ycbcr[1].stride = ycbcr[1].width;
110 ycbcr[1].height = (chroma_format == TH_PF_420) ? (yuv_h >> 1) : yuv_h;
111 ycbcr[2].width = ycbcr[1].width;
112 ycbcr[2].stride = ycbcr[1].stride;
113 ycbcr[2].height = ycbcr[1].height;
114#if 0
115 }
116#endif
117
118 delete [] ycbcr[0].data;
119 delete [] ycbcr[1].data;
120 delete [] ycbcr[2].data;
121
122 ycbcr[0].data = new uint8_t[ycbcr[0].stride * ycbcr[0].height];
123 ycbcr[1].data = new uint8_t[ycbcr[1].stride * ycbcr[1].height];
124 ycbcr[2].data = new uint8_t[ycbcr[2].stride * ycbcr[2].height];
125
126#if 0
127 ycbcr[0].data = new uint8_t[ycbcr[0].stride * ycbcr[0].height];
128 ycbcr[1].data = (m_PixelFormat == INDI_MONO) ? 0 : new uint8_t[ycbcr[1].stride * ycbcr[1].height];
129 ycbcr[2].data = (m_PixelFormat == INDI_MONO) ? 0 : new uint8_t[ycbcr[2].stride * ycbcr[2].height];
130#endif
131 }
132
133 return true;
134}
135
136bool TheoraRecorder::open(const char *filename, char *errmsg)
137{
139 return false;
140
141 if(soft_target)
142 {
143 if(video_rate <= 0)
144 {
145 snprintf(errmsg, ERRMSGSIZ, "Soft rate target requested without a bitrate.");
146 return false;
147 }
148
149 if(video_quality == -1)
150 video_quality = 0;
151 }
152 else
153 {
154 if(video_rate > 0)
155 video_quality = 0;
156 if(video_quality == -1)
157 video_quality = 48;
158 }
159
160 if(keyframe_frequency <= 0)
161 {
162 /*Use a default keyframe frequency of 64 for 1-pass (streaming) mode, and
163 256 for two-pass mode.*/
164 keyframe_frequency = twopass ? 256 : 64;
165 }
166
167 ogg_fp = fopen(filename, "wb");
168 if(!ogg_fp)
169 {
170 snprintf(errmsg, ERRMSGSIZ, "%s: error: could not open output file", filename);
171 return false;
172 }
173
174 srand(time(nullptr));
175 if(ogg_stream_init(&ogg_os, rand()))
176 {
177 snprintf(errmsg, ERRMSGSIZ, "%s: error: could not create ogg stream state", filename);
178 return false;
179 }
180
181 th_info_init(&ti);
182 ti.frame_width = ((rawWidth + 15) >> 4) << 4;
183 ti.frame_height = ((rawHeight + 15) >> 4) << 4;
184 ti.pic_width = rawWidth;
185 ti.pic_height = rawHeight;
186 ti.pic_x = 0;
187 ti.pic_y = 0;
188 frac(m_FPS, video_fps_numerator, video_fps_denominator);
189 ti.fps_numerator = video_fps_numerator;
190 ti.fps_denominator = video_fps_denominator;
191 ti.aspect_numerator = video_aspect_numerator;
192 ti.aspect_denominator = video_aspect_denominator;
193 ti.colorspace = TH_CS_UNSPECIFIED;
194 ti.pixel_fmt = static_cast<th_pixel_fmt>(chroma_format);
195 ti.target_bitrate = video_rate;
196 ti.quality = video_quality;
197 ti.keyframe_granule_shift = ilog(keyframe_frequency - 1);
198
199 td = th_encode_alloc(&ti);
200 th_info_clear(&ti);
201 /* setting just the granule shift only allows power-of-two keyframe
202 spacing. Set the actual requested spacing. */
203 int ret = th_encode_ctl(td, TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE, &keyframe_frequency, sizeof(keyframe_frequency - 1));
204 if(ret < 0)
205 {
206 snprintf(errmsg, ERRMSGSIZ, "Could not set keyframe interval to %d.", (int)keyframe_frequency);
207 }
208
209 if(vp3_compatible)
210 {
211 ret = th_encode_ctl(td, TH_ENCCTL_SET_VP3_COMPATIBLE, &vp3_compatible, sizeof(vp3_compatible));
212 if(ret < 0 || !vp3_compatible)
213 {
214 snprintf(errmsg, ERRMSGSIZ, "Could not enable strict VP3 compatibility.");
215 }
216 }
217
218 if(soft_target)
219 {
220 /* reverse the rate control flags to favor a 'long time' strategy */
221 int arg = TH_RATECTL_CAP_UNDERFLOW;
222 ret = th_encode_ctl(td, TH_ENCCTL_SET_RATE_FLAGS, &arg, sizeof(arg));
223 if(ret < 0)
224 snprintf(errmsg, ERRMSGSIZ, "Could not set encoder flags for soft-target");
225 /* Default buffer control is overridden on two-pass */
226 if(!twopass && buf_delay < 0)
227 {
228 if((keyframe_frequency * 7 >> 1) > 5 * video_fps_numerator / video_fps_denominator)
229 arg = keyframe_frequency * 7 >> 1;
230 else
231 arg = 5 * video_fps_numerator / video_fps_denominator;
232 ret = th_encode_ctl(td, TH_ENCCTL_SET_RATE_BUFFER, &arg, sizeof(arg));
233 if(ret < 0)
234 snprintf(errmsg, ERRMSGSIZ, "Could not set rate control buffer for soft-target");
235 }
236 }
237
238 /* set up two-pass if needed */
239 if(passno == 1)
240 {
241 unsigned char *buffer;
242 int bytes;
243 bytes = th_encode_ctl(td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer));
244 if(bytes < 0)
245 {
246 //IDLog("Could not set up the first pass of two-pass mode.");
247 //IDLog("Did you remember to specify an estimated bitrate?");
248 //exit(1);
249 return false;
250 }
251 /*Perform a seek test to ensure we can overwrite this placeholder data at
252 the end; this is better than letting the user sit through a whole
253 encode only to find out their pass 1 file is useless at the end.*/
254 if(fseek(twopass_file, 0, SEEK_SET) < 0)
255 {
256 //IDLog("Unable to seek in two-pass data file.");
257 //exit(1);
258 return false;
259 }
260 if(fwrite(buffer, 1, bytes, twopass_file) < static_cast<size_t>(bytes))
261 {
262 IDLog("Unable to write to two-pass data file.");
263 return false;
264 //exit(1);
265 }
266 fflush(twopass_file);
267 }
268 if(passno == 2)
269 {
270 /*Enable the second pass here.
271 We make this call just to set the encoder into 2-pass mode, because
272 by default enabling two-pass sets the buffer delay to the whole file
273 (because there's no way to explicitly request that behavior).
274 If we waited until we were actually encoding, it would overwrite our
275 settings.*/
276 if(th_encode_ctl(td, TH_ENCCTL_2PASS_IN, nullptr, 0) < 0)
277 {
278 snprintf(errmsg, ERRMSGSIZ, "Could not set up the second pass of two-pass mode.");
279 return false;
280 }
281 if(twopass == 3)
282 {
283 if(fseek(twopass_file, 0, SEEK_SET) < 0)
284 {
285 snprintf(errmsg, ERRMSGSIZ, "Unable to seek in two-pass data file.");
286 return false;
287 }
288 }
289 }
290 /*Now we can set the buffer delay if the user requested a non-default one
291 (this has to be done after two-pass is enabled).*/
292 if(passno != 1 && buf_delay >= 0)
293 {
294 ret = th_encode_ctl(td, TH_ENCCTL_SET_RATE_BUFFER, &buf_delay, sizeof(buf_delay));
295 if(ret < 0)
296 {
297 snprintf(errmsg, ERRMSGSIZ, "Warning: could not set desired buffer delay.");
298 }
299 }
300
301 /* write the bitstream header packets with proper page interleave */
302 th_comment_init(&tc);
303 /* first packet will get its own page automatically */
304 if(th_encode_flushheader(td, &tc, &op) <= 0)
305 {
306 snprintf(errmsg, ERRMSGSIZ, "Internal Theora library error.");
307 return false;
308 }
309
310 th_comment_clear(&tc);
311 if(passno != 1)
312 {
313 ogg_stream_packetin(&ogg_os, &op);
314 if(ogg_stream_pageout(&ogg_os, &og) != 1)
315 {
316 snprintf(errmsg, ERRMSGSIZ, "Internal Ogg library error.");
317 return false;
318 }
319
320 fwrite(og.header, 1, og.header_len, ogg_fp);
321 fwrite(og.body, 1, og.body_len, ogg_fp);
322 }
323
324 /* create the remaining theora headers */
325 for(;;)
326 {
327 ret = th_encode_flushheader(td, &tc, &op);
328 if(ret < 0)
329 {
330 snprintf(errmsg, ERRMSGSIZ, "Internal Theora library error.");
331 return false;
332 }
333 else if(!ret)
334 break;
335 if(passno != 1)
336 ogg_stream_packetin(&ogg_os, &op);
337 }
338 /* Flush the rest of our headers. This ensures
339 the actual data in each stream will start
340 on a new page, as per spec. */
341 if(passno != 1)
342 {
343 for(;;)
344 {
345 int result = ogg_stream_flush(&ogg_os, &og);
346 if(result < 0)
347 {
348 /* can't get here */
349 snprintf(errmsg, ERRMSGSIZ, "Internal Ogg library error.");
350 return false;
351 }
352 if(result == 0)
353 break;
354 fwrite(og.header, 1, og.header_len, ogg_fp);
355 fwrite(og.body, 1, og.body_len, ogg_fp);
356 }
357 }
358
359 isRecordingActive = true;
360
361 return true;
362}
363
365{
366 theora_write_frame(1);
367
368 if(passno == 1)
369 {
370 /* need to read the final (summary) packet */
371 unsigned char *buffer;
372 int bytes = th_encode_ctl(td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer));
373 if(bytes < 0)
374 {
375 IDLog("Could not read two-pass summary data from encoder.");
376 return false;
377 }
378 if(fseek(twopass_file, 0, SEEK_SET) < 0)
379 {
380 IDLog("Unable to seek in two-pass data file.");
381 return false;
382 }
383
384 if(fwrite(buffer, 1, bytes, twopass_file) < static_cast<size_t>(bytes))
385 {
386 IDLog("Unable to write to two-pass data file.");
387 return false;
388 }
389 fflush(twopass_file);
390 }
391
392 /*th_encode_free(td);
393 free(ycbcr[0].data);
394 ycbcr[0].data = nullptr;
395 free(ycbcr[1].data);
396 free(ycbcr[2].data);*/
397
398 if(ogg_stream_flush(&ogg_os, &og))
399 {
400 fwrite(og.header, og.header_len, 1, ogg_fp);
401 fwrite(og.body, og.body_len, 1, ogg_fp);
402 }
403
404 if(ogg_fp)
405 {
406 fflush(ogg_fp);
407 fclose(ogg_fp);
408 }
409
410 ogg_stream_clear(&ogg_os);
411 if(twopass_file)
412 fclose(twopass_file);
413
414 isRecordingActive = false;
415 return true;
416}
417
418bool TheoraRecorder::writeFrame(const uint8_t *frame, uint32_t nbytes, uint64_t)
419{
421 return false;
422
424 {
425 memcpy(ycbcr[0].data, frame, ycbcr[0].stride * ycbcr[0].height);
426 // Cb and Cr values to 0x80 (128) for grayscale image
427 memset(ycbcr[1].data, 0x80, ycbcr[1].stride * ycbcr[1].height);
428 memset(ycbcr[2].data, 0x80, ycbcr[2].stride * ycbcr[2].height);
429 }
430 else if (m_PixelFormat == INDI_RGB)
431 {
432 BGR2YUV(rawWidth, rawHeight, const_cast<uint8_t*>(frame), ycbcr[0].data, ycbcr[1].data, ycbcr[2].data, 0);
433 }
434 else if (m_PixelFormat == INDI_JPG)
435 {
436 decode_jpeg_raw((const_cast<uint8_t *>(frame)), nbytes, 0, 0, rawWidth, rawHeight, ycbcr[0].data, ycbcr[1].data,
437 ycbcr[2].data );
438 }
439 else
440 return false;
441
442 theora_write_frame(0);
443
444 return true;
445}
446
447# if 0
448bool TheoraRecorder::writeFrameMono(uint8_t *frame)
449{
450 if (isStreamingActive == false &&
451 (subX > 0 || subY > 0 || subW != rawWidth || subH != rawHeight))
452 {
453 int offset = ((rawWidth * subY) + subX);
454
455 uint8_t *srcBuffer = frame + offset;
456 uint8_t *destBuffer = frame;
457 int imageWidth = subW;
458 int imageHeight = subH;
459
460 for (int i = 0; i < imageHeight; i++)
461 memcpy(destBuffer + i * imageWidth, srcBuffer + rawWidth * i, imageWidth);
462 }
463
464 return writeFrame(frame);
465}
466
467bool TheoraRecorder::writeFrameColor(uint8_t *frame)
468{
469 if (isStreamingActive == false &&
470 (subX > 0 || subY > 0 || subW != rawWidth || subH != rawHeight))
471 {
472 int offset = ((rawWidth * subY) + subX);
473
474 uint8_t *srcBuffer = frame + offset * 3;
475 uint8_t *destBuffer = frame;
476 int imageWidth = subW;
477 int imageHeight = subH;
478
479 // RGB
480 for (int i = 0; i < imageHeight; i++)
481 memcpy(destBuffer + i * imageWidth * 3, srcBuffer + rawWidth * 3 * i, imageWidth * 3);
482 }
483
484 return writeFrame(frame);
485}
486#endif
487
488
489
490int TheoraRecorder::theora_write_frame(int last)
491{
492 ogg_packet op;
493 ogg_page og;
494
495 int rc = -1;
496
497 if( (rc = th_encode_ycbcr_in(td, ycbcr)) )
498 {
499 IDLog("error: could not encode frame %d", rc);
500 return rc;
501 }
502
503 /* in two-pass mode's first pass we need to extract and save the pass data */
504 if(passno == 1)
505 {
506 unsigned char *buffer;
507 int bytes = th_encode_ctl(td, TH_ENCCTL_2PASS_OUT, &buffer, sizeof(buffer));
508
509 if(bytes < 0)
510 {
511 IDLog("Could not read two-pass data from encoder.");
512 return 1;
513 }
514
515 if(fwrite(buffer, 1, bytes, twopass_file) < static_cast<size_t>(bytes))
516 {
517 IDLog("Unable to write to two-pass data file.");
518 return 1;
519 }
520
521 fflush(twopass_file);
522 }
523
524 if(!th_encode_packetout(td, last, &op))
525 {
526 IDLog("error: could not read packets");
527 return 1;
528 }
529
530 if (passno != 1)
531 {
532 ogg_stream_packetin(&ogg_os, &op);
533 while(ogg_stream_pageout(&ogg_os, &og))
534 {
535 fwrite(og.header, og.header_len, 1, ogg_fp);
536 fwrite(og.body, og.body_len, 1, ogg_fp);
537 }
538 }
539
540 return 0;
541}
542
543/*
544** find rational approximation to given real number
545** David Eppstein / UC Irvine / 8 Aug 1993
546**
547** With corrections from Arno Formella, May 2008
548**
549** usage: a.out r d
550** r is real number to approx
551** d is the maximum denominator allowed
552**
553** based on the theory of continued fractions
554** if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...)))
555** then best approximation is found by truncating this series
556** (with some adjustments in the last term).
557**
558** Note the fraction can be recovered as the first column of the matrix
559** ( a1 1 ) ( a2 1 ) ( a3 1 ) ...
560** ( 1 0 ) ( 1 0 ) ( 1 0 )
561** Instead of keeping the sequence of continued fraction terms,
562** we just keep the last partial product of these matrices.
563*/
564bool TheoraRecorder::frac(double fps, uint32_t &num, uint32_t &den)
565{
566 long m[2][2];
567 double x;
568 long maxden;
569 long ai;
570
571 x = fps;
572 maxden = 100;
573
574 /* initialize matrix */
575 m[0][0] = m[1][1] = 1;
576 m[0][1] = m[1][0] = 0;
577
578 /* loop finding terms until denom gets too big */
579 while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden)
580 {
581 long t;
582 t = m[0][0] * ai + m[0][1];
583 m[0][1] = m[0][0];
584 m[0][0] = t;
585 t = m[1][0] * ai + m[1][1];
586 m[1][1] = m[1][0];
587 m[1][0] = t;
588 if(x == (double)ai)
589 break; // AF: division by zero
590 x = 1 / (x - (double) ai);
591 if(x > (double)0x7FFFFFFF)
592 break; // AF: representation failure
593 }
594
595 num = m[0][0];
596 den = m[1][0];
597 return true;
598
599 /* now remaining x is between 0 and 1/ai */
600 /* approx as either 0 or 1/m where m is max that will fit in maxden */
601 /* first try zero */
602 //printf("%ld/%ld, error = %e\n", m[0][0], m[1][0], startx - ((double) m[0][0] / (double) m[1][0]));
603
604 /* now try other possibility */
605 //ai = (maxden - m[1][1]) / m[1][0];
606 //m[0][0] = m[0][0] * ai + m[0][1];
607 //m[1][0] = m[1][0] * ai + m[1][1];
608 //printf("%ld/%ld, error = %e\n", m[0][0], m[1][0],
609 // startx - ((double) m[0][0] / (double) m[1][0]));
610}
611}
int BGR2YUV(int x_dim, int y_dim, void *bmp, void *y_out, void *u_out, void *v_out, int flip)
Definition ccvt_misc.c:896
virtual bool setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth)
virtual bool writeFrame(const uint8_t *frame, uint32_t nbytes, uint64_t timestamp)
virtual bool open(const char *filename, char *errmsg)
INDI_PIXEL_FORMAT m_PixelFormat
virtual bool setSize(uint16_t width, uint16_t height)
INDI_PIXEL_FORMAT
@ INDI_MONO
@ INDI_RGB
@ INDI_JPG
void IDLog(const char *fmt,...)
Definition indicom.c:305
int decode_jpeg_raw(unsigned char *jpeg_data, int len, int itype, int ctype, unsigned int width, unsigned int height, unsigned char *raw0, unsigned char *raw1, unsigned char *raw2)
decode JPEG buffer
Definition jpegutils.c:461
std::vector< uint8_t > buffer
Namespace to encapsulate INDI client, drivers, and mediator classes.
#define ERRMSGSIZ
Definition v4l2driver.h:38