Instrument Neutral Distributed Interface INDI  2.0.2
theorarecorder.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2017 by Jasem Mutlaq <mutlaqja@ikarustech.com>
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 
35 static int ilog(unsigned _v)
36 {
37  int ret;
38  for(ret = 0; _v; ret++)_v >>= 1;
39  return ret;
40 }
41 
42 namespace 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 
64 bool TheoraRecorder::setPixelFormat(INDI_PIXEL_FORMAT pixelFormat, uint8_t pixelDepth)
65 {
66  m_PixelFormat = pixelFormat;
67  m_PixelDepth = pixelDepth;
68  return true;
69 }
70 
71 bool 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 
82 bool 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
96  if (m_PixelFormat == INDI_MONO)
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 
136 bool TheoraRecorder::open(const char *filename, char *errmsg)
137 {
138  if (isRecordingActive)
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 overwite 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 
418 bool TheoraRecorder::writeFrame(const uint8_t *frame, uint32_t nbytes, uint64_t)
419 {
420  if (!isRecordingActive)
421  return false;
422 
423  if (m_PixelFormat == INDI_MONO)
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
448 bool 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 
467 bool 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 
490 int 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 */
564 bool 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:894
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
Definition: indibasetypes.h:70
@ INDI_MONO
Definition: indibasetypes.h:71
@ INDI_RGB
Definition: indibasetypes.h:80
@ INDI_JPG
Definition: indibasetypes.h:82
void IDLog(const char *fmt,...)
Definition: indicom.c:316
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