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