openMSX
OggReader.cc
Go to the documentation of this file.
1 #include "OggReader.hh"
2 #include "MSXException.hh"
3 #include "yuv2rgb.hh"
4 #include "likely.hh"
5 #include "CliComm.hh"
6 #include "MemoryOps.hh"
7 #include "one_of.hh"
8 #include "ranges.hh"
9 #include "stl.hh"
10 #include "stringsp.hh" // for strncasecmp
11 #include "view.hh"
12 #include "xrange.hh"
13 #include <cstring> // for memcpy, memcmp
14 #include <cstdlib> // for atoi
15 #include <cctype> // for isspace
16 #include <memory>
17 
18 // TODO
19 // - Improve error handling
20 // - When an non-ogg file is passed, the entire file is scanned
21 // - Clean up this mess!
22 namespace openmsx {
23 
24 Frame::Frame(const th_ycbcr_buffer& yuv)
25 {
26  unsigned y_size = yuv[0].height * yuv[0].stride;
27  unsigned uv_size = yuv[1].height * yuv[1].stride;
28 
29  buffer[0] = yuv[0];
30  buffer[0].data = static_cast<unsigned char*>(
31  MemoryOps::mallocAligned(16, y_size));
32  buffer[1] = yuv[1];
33  buffer[1].data = static_cast<unsigned char*>(
34  MemoryOps::mallocAligned(16, uv_size));
35  buffer[2] = yuv[2];
36  buffer[2].data = static_cast<unsigned char*>(
37  MemoryOps::mallocAligned(16, uv_size));
38 }
39 
41 {
45 }
46 
47 
49  : cli(cli_)
50  , file(filename)
51 {
52  audioSerial = -1;
53  videoSerial = -1;
54  skeletonSerial = -1;
55  audioHeaders = 3;
56  keyFrame = size_t(-1);
57  currentSample = 0;
58  currentFrame = 1;
59  vorbisPos = 0;
60 
61  th_info ti;
62  th_comment tc;
63  th_setup_info* tsi = nullptr;
64 
65  th_info_init(&ti);
66  th_comment_init(&tc);
67  theora = nullptr;
68 
69  vorbis_info_init(&vi);
70  vorbis_comment_init(&vc);
71 
72  ogg_sync_init(&sync);
73 
74  state = PLAYING;
75  fileOffset = 0;
76  fileSize = file.getSize();
77 
78  ogg_page page;
79 
80  try {
81  while ((audioHeaders || !theora) && nextPage(&page)) {
82  int serial = ogg_page_serialno(&page);
83 
84  if (serial == audioSerial) {
85  vorbisHeaderPage(&page);
86  continue;
87  } else if (serial == videoSerial) {
88  theoraHeaderPage(&page, ti, tc, tsi);
89  continue;
90  } else if (serial == skeletonSerial) {
91  continue;
92  }
93 
94  if (!ogg_page_bos(&page)) {
95  if (videoSerial == -1) {
96  throw MSXException("No video track found");
97  }
98 
99  if (audioSerial == -1) {
100  throw MSXException("No audio track found");
101  }
102 
103  // This should be unreachable, right?
104  continue;
105  }
106 
107  ogg_stream_state stream;
108  ogg_packet packet;
109 
110  ogg_stream_init(&stream, serial);
111  ogg_stream_pagein(&stream, &page);
112  if (ogg_stream_packetout(&stream, &packet) <= 0) {
113  ogg_stream_clear(&stream);
114  throw MSXException("Invalid header");
115  }
116 
117  if (packet.bytes < 8) {
118  ogg_stream_clear(&stream);
119  throw MSXException("Header to small");
120  }
121 
122  if (memcmp(packet.packet, "\x01vorbis", 7) == 0) {
123  if (audioSerial != -1) {
124  ogg_stream_clear(&stream);
125  throw MSXException("Duplicate audio stream");
126  }
127 
128  audioSerial = serial;
129  ogg_stream_init(&vorbisStream, serial);
130 
131  vorbisHeaderPage(&page);
132 
133  } else if (memcmp(packet.packet, "\x80theora", 7) == 0) {
134  if (videoSerial != -1) {
135  ogg_stream_clear(&stream);
136  throw MSXException("Duplicate video stream");
137  }
138 
139  videoSerial = serial;
140  ogg_stream_init(&theoraStream, serial);
141 
142  theoraHeaderPage(&page, ti, tc, tsi);
143 
144  } else if (memcmp(packet.packet, "fishead", 8) == 0) {
145  skeletonSerial = serial;
146 
147  } else if (memcmp(packet.packet, "BBCD", 4) == 0) {
148  ogg_stream_clear(&stream);
149  throw MSXException("DIRAC not supported");
150 
151  } else if (memcmp(packet.packet, "\177FLAC", 5) == 0) {
152  ogg_stream_clear(&stream);
153  throw MSXException("FLAC not supported");
154 
155  } else {
156  ogg_stream_clear(&stream);
157  throw MSXException("Unknown stream in ogg file");
158  }
159 
160  ogg_stream_clear(&stream);
161  }
162 
163  if (videoSerial == -1) {
164  throw MSXException("No video track found");
165  }
166 
167  if (audioSerial == -1) {
168  throw MSXException("No audio track found");
169  }
170 
171  if (vi.channels != 2) {
172  throw MSXException("Audio must be stereo");
173  }
174 
175  if (ti.frame_width != 640 || ti.frame_height != 480) {
176  throw MSXException("Video must be size 640x480");
177  }
178 
179  if (ti.fps_numerator == 30000 && ti.fps_denominator == 1001) {
180  frameRate = 30;
181  } else if (ti.fps_numerator == 60000 && ti.fps_denominator == 1001) {
182  frameRate = 60;
183  } else {
184  throw MSXException("Video frame rate must be 59.94Hz or 29.97Hz");
185  }
186 
187  // FIXME: Support YUV444 before release
188  // It would be much better to use YUV444, however the existing
189  // captures are in YUV420 format. yuv2rgb will have to be
190  // updated too.
191  if (ti.pixel_fmt != TH_PF_420) {
192  throw MSXException("Video must be YUV420");
193  }
194  }
195  catch (MSXException&) {
196  th_setup_free(tsi);
197  th_info_clear(&ti);
198  th_comment_clear(&tc);
199  cleanup();
200  throw;
201  }
202 
203  th_setup_free(tsi);
204  th_info_clear(&ti);
205  th_comment_clear(&tc);
206 }
207 
208 void OggReader::cleanup()
209 {
210  if (audioHeaders == 0) {
211  vorbis_dsp_clear(&vd);
212  vorbis_block_clear(&vb);
213  }
214 
215  th_decode_free(theora);
216 
217  vorbis_info_clear(&vi);
218  vorbis_comment_clear(&vc);
219  if (audioSerial != -1) {
220  ogg_stream_clear(&vorbisStream);
221  }
222 
223  if (videoSerial != -1) {
224  ogg_stream_clear(&theoraStream);
225  }
226 
227  ogg_sync_clear(&sync);
228 }
229 
231 {
232  cleanup();
233 }
234 
240 void OggReader::vorbisFoundPosition()
241 {
242  auto last = vorbisPos;
243  for (auto& audioFrag : view::reverse(audioList)) {
244  last -= audioFrag->length;
245  audioFrag->position = last;
246  }
247 
248  // last is now the first vorbis audio decoded
249  if (last > currentSample) {
250  cli.printWarning("missing part of audio stream");
251  }
252 
253  if (vorbisPos > currentSample) {
254  currentSample = vorbisPos;
255  }
256 }
257 
258 
259 void OggReader::vorbisHeaderPage(ogg_page* page)
260 {
261  ogg_stream_pagein(&vorbisStream, page);
262 
263  while (true) {
264  ogg_packet packet;
265 
266  int res = ogg_stream_packetout(&vorbisStream, &packet);
267  if (res < 0) {
268  throw MSXException("error in vorbis stream");
269  }
270  if (res == 0) break;
271 
272  if (audioHeaders == 0) {
273  // ignore, we'll seek to the beginning again before playing
274  continue;
275  }
276 
277  if (packet.packetno <= 2) {
278  if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) {
279  throw MSXException("invalid vorbis header");
280  }
281  --audioHeaders;
282  }
283 
284  if (packet.packetno == 2) {
285  vorbis_synthesis_init(&vd, &vi) ;
286  vorbis_block_init(&vd, &vb);
287  }
288  }
289 }
290 
291 void OggReader::theoraHeaderPage(ogg_page* page, th_info& ti, th_comment& tc,
292  th_setup_info*& tsi)
293 {
294  ogg_stream_pagein(&theoraStream, page);
295 
296  while (true) {
297  ogg_packet packet;
298 
299  int res = ogg_stream_packetout(&theoraStream, &packet);
300  if (res < 0) {
301  throw MSXException("error in vorbis stream");
302  }
303  if (res == 0) break;
304 
305  if (theora) {
306  // ignore, we'll seek to the beginning again before playing
307  continue;
308  }
309 
310  if (packet.packetno <= 2) {
311  res = th_decode_headerin(&ti, &tc, &tsi, &packet);
312  if (res <= 0) {
313  throw MSXException("invalid theora header");
314  }
315  }
316 
317  if (packet.packetno == 2) {
318  theora = th_decode_alloc(&ti, tsi);
319  readMetadata(tc);
320  granuleShift = ti.keyframe_granule_shift;
321  }
322  }
323 }
324 
325 void OggReader::readVorbis(ogg_packet* packet)
326 {
327  // deal with header packets
328  if (unlikely(packet->packetno <= 2)) {
329  return;
330  }
331 
332  if (state == FIND_LAST) {
333  if (packet->granulepos != -1) {
334  if ((currentSample == AudioFragment::UNKNOWN_POS) ||
335  (size_t(packet->granulepos) > currentSample)) {
336  currentSample = packet->granulepos;
337  }
338  }
339  return;
340 
341  } else if (state == FIND_FIRST) {
342  if (packet->granulepos != -1 &&
343  currentSample == AudioFragment::UNKNOWN_POS) {
344  currentSample = packet->granulepos;
345  }
346  return;
347 
348  } else if (state == FIND_KEYFRAME) {
349  // Not relevant for vorbis
350  return;
351  }
352 
353  // generate pcm
354  if (vorbis_synthesis(&vb, packet) != 0) {
355  return;
356  }
357 
358  vorbis_synthesis_blockin(&vd, &vb);
359 
360  float** pcm;
361  long decoded = vorbis_synthesis_pcmout(&vd, &pcm);
362  long pos = 0;
363 
364  while (pos < decoded) {
365  // Find memory to copy PCM into
366  if (recycleAudioList.empty()) {
367  auto audio = std::make_unique<AudioFragment>();
368  audio->length = 0;
369  recycleAudioList.push_back(std::move(audio));
370  }
371  auto& audio = recycleAudioList.front();
372  if (audio->length == 0) {
373  audio->position = vorbisPos;
374  } else {
375  // first element was already partially filled
376  }
377 
378  // Copy PCM
379  unsigned len = std::min<long>(decoded - pos,
380  AudioFragment::MAX_SAMPLES - audio->length);
381 
382  memcpy(audio->pcm[0] + audio->length, pcm[0] + pos,
383  len * sizeof(float));
384  memcpy(audio->pcm[1] + audio->length, pcm[1] + pos,
385  len * sizeof(float));
386 
387  audio->length += len;
388  pos += len;
389 
390  // Last packet or found position after seeking?
391  bool last = (decoded == pos && (packet->e_o_s ||
392  (vorbisPos == AudioFragment::UNKNOWN_POS &&
393  packet->granulepos != -1)));
394 
395  if (vorbisPos != AudioFragment::UNKNOWN_POS) {
396  vorbisPos += len;
397  currentSample += len;
398  }
399 
400  if (audio->length == AudioFragment::MAX_SAMPLES || last) {
401  audioList.push_back(recycleAudioList.pop_front());
402  }
403  }
404 
405  // The granulepos is the no. of samples since the begining of the
406  // stream. Only once per ogg page is this populated.
407  if (packet->granulepos != -1) {
408  if (vorbisPos == AudioFragment::UNKNOWN_POS) {
409  vorbisPos = packet->granulepos;
410  vorbisFoundPosition();
411  } else {
412  if (vorbisPos != size_t(packet->granulepos)) {
413  cli.printWarning(
414  "vorbis audio out of sync, expected ",
415  vorbisPos, ", got ", packet->granulepos);
416  vorbisPos = packet->granulepos;
417  }
418  }
419  }
420 
421  // done with PCM data
422  vorbis_synthesis_read(&vd, decoded);
423 }
424 
425 size_t OggReader::frameNo(ogg_packet* packet) const
426 {
427  if (packet->granulepos == -1) {
428  return size_t(-1);
429  }
430 
431  size_t intra = packet->granulepos & ((size_t(1) << granuleShift) - 1);
432  size_t key = packet->granulepos >> granuleShift;
433  return key + intra;
434 }
435 
436 void OggReader::readMetadata(th_comment& tc)
437 {
438  char* metadata = nullptr;
439  for (auto i : xrange(tc.comments)) {
440  if (!strncasecmp(tc.user_comments[i], "location=",
441  strlen("location="))) {
442  metadata = tc.user_comments[i] + strlen("location=");
443  break;
444  }
445  }
446  if (!metadata) {
447  return;
448  }
449 
450  // Maybe there is a better way of doing this parsing in C++
451  char* p = metadata;
452  while (p) {
453  while (isspace(*p)) {
454  p++;
455  }
456  if (strncasecmp(p, "chapter: ", 9) == 0) {
457  int chapter = atoi(p + 9);
458  p = strchr(p, ',');
459  if (!p) break;
460  ++p;
461  size_t frame = atol(p);
462  if (frame) {
463  chapters.emplace_back(chapter, frame);
464  }
465  } else if (strncasecmp(p, "stop: ", 6) == 0) {
466  size_t stopframe = atol(p + 6);
467  if (stopframe) {
468  stopFrames.push_back(stopframe);
469  }
470  }
471  p = strchr(p, '\n');
472  if (p) ++p;
473  }
474  ranges::sort(stopFrames);
475  ranges::sort(chapters, LessTupleElement<0>());
476 }
477 
478 void OggReader::readTheora(ogg_packet* packet)
479 {
480  if (th_packet_isheader(packet)) {
481  return;
482  }
483 
484  size_t frameno = frameNo(packet);
485 
486  // If we're seeking, we're only interested in packets with
487  // frame numbers
488  if ((state != PLAYING) && (frameno == size_t(-1))) {
489  return;
490  }
491 
492  if (state == FIND_LAST) {
493  if ((currentFrame == size_t(-1)) || (currentFrame < frameno)) {
494  currentFrame = frameno;
495  }
496  return;
497 
498  } else if (state == FIND_FIRST) {
499  if ((currentFrame == size_t(-1)) || (currentFrame > frameno)) {
500  currentFrame = frameno;
501  }
502  return;
503 
504  } else if (state == FIND_KEYFRAME) {
505  if (frameno < currentFrame) {
506  keyFrame = packet->granulepos >> granuleShift;
507  } else if (currentFrame == frameno) {
508  keyFrame = packet->granulepos >> granuleShift;
509  currentSample = 1;
510  } else if (frameno > currentFrame) {
511  currentSample = 1;
512  }
513  return;
514  }
515 
516  if ((keyFrame != size_t(-1)) && (frameno != size_t(-1)) &&
517  (frameno < keyFrame)) {
518  // We're reading before the keyframe, discard
519  return;
520  }
521 
522  if (packet->bytes == 0 && frameList.empty()) {
523  // No use passing empty packets (which represent dup frame)
524  // before we've read any frame.
525  return;
526  }
527 
528  keyFrame = size_t(-1);
529 
530  int rc = th_decode_packetin(theora, packet, nullptr);
531  switch (rc) {
532  case TH_DUPFRAME:
533  if (frameList.empty()) {
534  cli.printWarning("Theora error: dup frame encountered "
535  "without preceding frame");
536  } else {
537  frameList.back()->length++;
538  }
539  break;
540  case TH_EIMPL:
541  cli.printWarning("Theora error: not capable of reading this");
542  break;
543  case TH_EFAULT:
544  cli.printWarning("Theora error: API not used correctly");
545  break;
546  case TH_EBADPACKET:
547  cli.printWarning("Theora error: bad packet");
548  break;
549  case 0:
550  break;
551  default:
552  cli.printWarning("Theora error: unknown error ", rc);
553  break;
554  }
555 
556  if (rc) {
557  return;
558  }
559 
560  th_ycbcr_buffer yuv;
561 
562  if (th_decode_ycbcr_out(theora, yuv) != 0) {
563  return;
564  }
565 
566  if ((frameno != size_t(-1)) && (frameno < currentFrame)) {
567  return;
568  }
569 
570  currentFrame = frameno + 1;
571 
572  std::unique_ptr<Frame> frame;
573  if (recycleFrameList.empty()) {
574  frame = std::make_unique<Frame>(yuv);
575  } else {
576  frame = std::move(recycleFrameList.back());
577  recycleFrameList.pop_back();
578  }
579 
580  int y_size = yuv[0].height * yuv[0].stride;
581  int uv_size = yuv[1].height * yuv[1].stride;
582  memcpy(frame->buffer[0].data, yuv[0].data, y_size);
583  memcpy(frame->buffer[1].data, yuv[1].data, uv_size);
584  memcpy(frame->buffer[2].data, yuv[2].data, uv_size);
585 
586  // At lot of frames have framenumber -1, only some have the correct
587  // frame number. We continue counting from the previous known
588  // postion
589  Frame* last = frameList.empty() ? nullptr : frameList.back().get();
590  if (last && (last->no != size_t(-1))) {
591  if (frameno != one_of(size_t(-1), last->no + last->length)) {
592  cli.printWarning("Theora frame sequence wrong");
593  } else {
594  frameno = last->no + last->length;
595  }
596  }
597 
598  frame->no = frameno;
599  frame->length = 1;
600 
601  // We may read some frames before we encounter one with a proper
602  // frame number. When we do, go back and populate the frame
603  // numbers correctly
604  if (!frameList.empty() && (frameno != size_t(-1)) &&
605  (frameList[0]->no == size_t(-1))) {
606  for (auto& frm : view::reverse(frameList)) {
607  frameno -= frm->length;
608  frm->no = frameno;
609  }
610  }
611 
612  frameList.push_back(std::move(frame));
613 }
614 
615 void OggReader::getFrameNo(RawFrame& rawFrame, size_t frameno)
616 {
617  Frame* frame;
618  while (true) {
619  // If there are no frames or the frames we have read
620  // does not include a proper frame number, just read
621  // more data
622  if (frameList.empty() || (frameList[0]->no == size_t(-1))) {
623  if (!nextPacket()) {
624  return;
625  }
626  continue;
627  }
628 
629  // Remove unneeded frames. Note that at 60Hz the odd and
630  // and even frame are displayed during still, so we can
631  // only throw away the one two frames ago
632  while (frameList.size() >= 3 && frameList[2]->no <= frameno) {
633  recycleFrameList.push_back(frameList.pop_front());
634  }
635 
636  if (!frameList.empty() && frameList[0]->no > frameno) {
637  // we're missing frames!
638  frame = frameList[0].get();
639  cli.printWarning(
640  "Cannot find frame ", frameno, " using ",
641  frame->no, " instead");
642  break;
643  }
644 
645  if ((frameList.size() >= 2) &&
646  ((frameno >= frameList[0]->no) &&
647  (frameno < frameList[1]->no))) {
648  frame = frameList[0].get();
649  break;
650  }
651 
652  if ((frameList.size() >= 3) &&
653  ((frameno >= frameList[1]->no) &&
654  (frameno < frameList[2]->no))) {
655  frame = frameList[1].get();
656  break;
657  }
658 
659  // Sanity check, should not happen
660  if (frameList.size() > (size_t(2) << granuleShift)) {
661  // We've got more than twice as many frames
662  // as the maximum distance between key frames.
663  cli.printWarning("Cannot find frame ", frameno);
664  return;
665  }
666 
667  // ..add read some new ones
668  if (!nextPacket()) {
669  return;
670  }
671  }
672 
673  yuv2rgb::convert(frame->buffer, rawFrame);
674 }
675 
676 void OggReader::recycleAudio(std::unique_ptr<AudioFragment> audio)
677 {
678  audio->length = 0;
679  recycleAudioList.push_back(std::move(audio));
680 }
681 
682 const AudioFragment* OggReader::getAudio(size_t sample)
683 {
684  // Read while position is unknown
685  while (audioList.empty() ||
686  audioList.front()->position == AudioFragment::UNKNOWN_POS) {
687  if (!nextPacket()) {
688  return nullptr;
689  }
690  }
691 
692  auto it = begin(audioList);
693  while (true) {
694  auto& audio = *it;
695  if (audio->position + audio->length + getSampleRate() <= sample) {
696  // Dispose if this, more than 1 second old
697  recycleAudio(std::move(*it));
698  it = audioList.erase(it);
699  } else if (audio->position + audio->length <= sample) {
700  ++it;
701  } else {
702  if (audio->position <= sample) {
703  return audio.get();
704  } else {
705  // gone too far?
706  return nullptr;
707  }
708  }
709 
710  // read more if we're at the end of the list
711  if (it == end(audioList)) {
712  size_t size = audioList.size();
713  while (size == audioList.size()) {
714  if (!nextPacket()) {
715  return nullptr;
716  }
717  }
718 
719  // reset the iterator to not point to the end
720  it = begin(audioList);
721  }
722  }
723 }
724 
725 bool OggReader::nextPacket()
726 {
727  ogg_packet packet;
728  ogg_page page;
729 
730  while (true) {
731  int ret = ogg_stream_packetout(&vorbisStream, &packet);
732  if (ret == 1) {
733  readVorbis(&packet);
734  return true;
735  } else if (ret == -1) {
736  // recoverable error
737  continue;
738  }
739 
740  ret = ogg_stream_packetout(&theoraStream, &packet);
741  if (ret == 1) {
742  readTheora(&packet);
743  return true;
744  } else if (ret == -1) {
745  // recoverable error
746  continue;
747  }
748 
749  if (!nextPage(&page)) {
750  return false;
751  }
752 
753  int serial = ogg_page_serialno(&page);
754  if (serial == audioSerial) {
755  if (ogg_stream_pagein(&vorbisStream, &page)) {
756  cli.printWarning("Failed to submit vorbis page");
757  }
758  } else if (serial == videoSerial) {
759  if (ogg_stream_pagein(&theoraStream, &page)) {
760  cli.printWarning("Failed to submit theora page");
761  }
762  } else if (serial != skeletonSerial) {
763  cli.printWarning("Unexpected stream with serial ",
764  serial, " in ogg file");
765  }
766  }
767 }
768 
769 
770 bool OggReader::nextPage(ogg_page* page)
771 {
772  constexpr size_t CHUNK = 4096;
773 
774  int ret;
775  while ((ret = ogg_sync_pageseek(&sync, page)) <= 0) {
776  if (ret < 0) {
777  //throw MSXException("Invalid Ogg file");
778  }
779 
780  size_t chunk;
781  if (fileSize - fileOffset >= CHUNK) {
782  chunk = CHUNK;
783  } else if (fileOffset < fileSize) {
784  chunk = fileSize - fileOffset;
785  } else {
786  return false;
787  }
788 
789  char* buffer = ogg_sync_buffer(&sync, long(chunk));
790  file.read(buffer, chunk);
791  fileOffset += chunk;
792 
793  if (ogg_sync_wrote(&sync, long(chunk)) == -1) {
794  cli.printWarning("Internal error: ogg_sync_wrote failed");
795  }
796  }
797 
798  return true;
799 }
800 
801 size_t OggReader::bisection(
802  size_t frame, size_t sample,
803  size_t maxOffset, size_t maxSamples, size_t maxFrames)
804 {
805  // Defined to be a power-of-two such that the calculations can be done faster.
806  // Note that the sample-number is in the range of: 1..(44100*60*60)
807  constexpr uint64_t SHIFT = 0x20000000ull;
808 
809  uint64_t offsetA = 0, offsetB = maxOffset;
810  uint64_t sampleA = 0, sampleB = maxSamples;
811  uint64_t frameA = 1, frameB = maxFrames;
812 
813  while (true) {
814  uint64_t ratio = (frame - frameA) * SHIFT / (frameB - frameA);
815  if (ratio < 5) {
816  return offsetA;
817  }
818 
819  uint64_t frameOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
820  ratio = (sample - sampleA) * SHIFT / (sampleB - sampleA);
821  if (ratio < 5) {
822  return offsetA;
823  }
824  uint64_t sampleOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
825  auto offset = std::min(sampleOffset, frameOffset);
826 
827  file.seek(offset);
828  fileOffset = offset;
829  ogg_sync_reset(&sync);
830  currentFrame = size_t(-1);
831  currentSample = AudioFragment::UNKNOWN_POS;
832  state = FIND_FIRST;
833 
834  while (((currentFrame == size_t(-1)) ||
835  (currentSample == AudioFragment::UNKNOWN_POS)) &&
836  nextPacket()) {
837  // continue reading
838  }
839 
840  state = PLAYING;
841 
842  if (currentSample > sample || currentFrame > frame) {
843  offsetB = offset;
844  sampleB = currentSample;
845  frameB = currentFrame;
846  } else if (currentSample + getSampleRate() < sample &&
847  currentFrame + 64 < frame) {
848  offsetA = offset;
849  sampleA = currentSample;
850  frameA = currentFrame;
851  } else {
852  return offset;
853  }
854  }
855 }
856 
857 size_t OggReader::findOffset(size_t frame, size_t sample)
858 {
859  constexpr size_t STEP = 32 * 1024;
860 
861  // first calculate total length in bytes, samples and frames
862 
863  // The file might have changed since we last requested its size,
864  // we assume that only data will be added to it and the ogg streams
865  // are exactly as before
866  fileSize = file.getSize();
867  auto offset = fileSize - 1;
868 
869  while (offset > 0) {
870  if (offset > STEP) {
871  offset -= STEP;
872  } else {
873  offset = 0;
874  }
875 
876  file.seek(offset);
877  fileOffset = offset;
878  ogg_sync_reset(&sync);
879  currentFrame = size_t(-1);
880  currentSample = AudioFragment::UNKNOWN_POS;
881  state = FIND_LAST;
882 
883  while (nextPacket()) {
884  // continue reading
885  }
886 
887  state = PLAYING;
888 
889  if ((currentFrame != size_t(-1)) &&
890  (currentSample != AudioFragment::UNKNOWN_POS)) {
891  break;
892  }
893  }
894 
895  totalFrames = currentFrame;
896 
897  // If we're close to beginning, don't bother searching for it,
898  // just start at the beginning (arbitrary boundary of 1 second).
899  if (sample < getSampleRate() || frame <= 30) {
900  keyFrame = 1;
901  return 0;
902  }
903 
904  auto maxOffset = offset;
905  auto maxSamples = currentSample;
906  auto maxFrames = currentFrame;
907 
908  if ((sample > maxSamples) || (frame > maxFrames)) {
909  sample = maxSamples;
910  frame = maxFrames;
911  }
912 
913  offset = bisection(frame, sample, maxOffset, maxSamples, maxFrames);
914 
915  // Find key frame
916  file.seek(offset);
917  fileOffset = offset;
918  ogg_sync_reset(&sync);
919  currentFrame = frame;
920  currentSample = 0;
921  keyFrame = size_t(-1);
922  state = FIND_KEYFRAME;
923 
924  while (currentSample == 0 && nextPacket()) {
925  // continue reading
926  }
927 
928  state = PLAYING;
929 
930  if (keyFrame == one_of(size_t(-1), frame)) {
931  return offset;
932  }
933 
934  return bisection(keyFrame, sample, maxOffset, maxSamples, maxFrames);
935 }
936 
937 bool OggReader::seek(size_t frame, size_t samples)
938 {
939  // Remove all queued frames
940  recycleFrameList.insert(end(recycleFrameList),
941  std::move_iterator(begin(frameList)),
942  std::move_iterator(end (frameList)));
943  frameList.clear();
944 
945  // Remove all queued audio
946  if (!recycleAudioList.empty()) {
947  recycleAudioList.front()->length = 0;
948  }
949  for (auto& a : audioList) {
950  recycleAudio(std::move(a));
951  }
952  audioList.clear();
953 
954  fileOffset = findOffset(frame, samples);
955  file.seek(fileOffset);
956 
957  ogg_sync_reset(&sync);
958 
959  vorbisPos = AudioFragment::UNKNOWN_POS;
960  currentFrame = frame;
961  currentSample = samples;
962 
963  vorbis_synthesis_restart(&vd);
964 
965  return true;
966 }
967 
968 bool OggReader::stopFrame(size_t frame) const
969 {
970  return ranges::binary_search(stopFrames, frame);
971 }
972 
973 size_t OggReader::getChapter(int chapterNo) const
974 {
975  auto it = ranges::lower_bound(chapters, chapterNo, LessTupleElement<0>());
976  return ((it != end(chapters)) && (it->first == chapterNo))
977  ? it->second : 0;
978 }
979 
980 } // namespace openmsx
Definition: one_of.hh:7
void printWarning(std::string_view message)
Definition: CliComm.cc:10
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition: File.cc:118
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:93
size_t getSize()
Returns the size of this file.
Definition: File.cc:113
This class represents a filename.
Definition: Filename.hh:18
bool stopFrame(size_t frame) const
Definition: OggReader.cc:968
void getFrameNo(RawFrame &frame, size_t frameno)
Definition: OggReader.cc:615
const AudioFragment * getAudio(size_t sample)
Definition: OggReader.cc:682
OggReader(const OggReader &)=delete
unsigned getSampleRate() const
Definition: OggReader.hh:49
size_t getChapter(int chapterNo) const
Definition: OggReader.cc:973
bool seek(size_t frame, size_t sample)
Definition: OggReader.cc:937
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:14
#define unlikely(x)
Definition: likely.hh:15
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
void * mallocAligned(size_t alignment, size_t size)
Definition: MemoryOps.cc:254
void freeAligned(void *)
Definition: MemoryOps.cc:284
void convert(const th_ycbcr_buffer &input, RawFrame &output)
Definition: yuv2rgb.cc:342
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr const char *const filename
bool binary_search(ForwardRange &&range, const T &value)
Definition: ranges.hh:59
auto lower_bound(ForwardRange &&range, const T &value)
Definition: ranges.hh:71
void sort(RandomAccessRange &&range)
Definition: ranges.hh:35
size_t size(std::string_view utf8)
constexpr auto reverse(Range &&range)
Definition: view.hh:300
static constexpr size_t UNKNOWN_POS
Definition: OggReader.hh:22
static constexpr unsigned MAX_SAMPLES
Definition: OggReader.hh:23
Frame(const th_ycbcr_buffer &yuv)
Definition: OggReader.cc:24
th_ycbcr_buffer buffer
Definition: OggReader.hh:34
constexpr auto xrange(T e)
Definition: xrange.hh:155
constexpr auto begin(const zstring_view &x)
Definition: zstring_view.hh:82
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:83