26 unsigned y_size = yuv[0].height * yuv[0].stride;
27 unsigned uv_size = yuv[1].height * yuv[1].stride;
30 buffer[0].data =
static_cast<unsigned char*
>(
33 buffer[1].data =
static_cast<unsigned char*
>(
36 buffer[2].data =
static_cast<unsigned char*
>(
56 keyFrame = size_t(-1);
63 th_setup_info* tsi =
nullptr;
69 vorbis_info_init(&vi);
70 vorbis_comment_init(&vc);
81 while ((audioHeaders || !theora) && nextPage(&page)) {
82 int serial = ogg_page_serialno(&page);
84 if (serial == audioSerial) {
85 vorbisHeaderPage(&page);
87 }
else if (serial == videoSerial) {
88 theoraHeaderPage(&page, ti, tc, tsi);
90 }
else if (serial == skeletonSerial) {
94 if (!ogg_page_bos(&page)) {
95 if (videoSerial == -1) {
99 if (audioSerial == -1) {
107 ogg_stream_state stream;
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);
117 if (packet.bytes < 8) {
118 ogg_stream_clear(&stream);
122 if (memcmp(packet.packet,
"\x01vorbis", 7) == 0) {
123 if (audioSerial != -1) {
124 ogg_stream_clear(&stream);
128 audioSerial = serial;
129 ogg_stream_init(&vorbisStream, serial);
131 vorbisHeaderPage(&page);
133 }
else if (memcmp(packet.packet,
"\x80theora", 7) == 0) {
134 if (videoSerial != -1) {
135 ogg_stream_clear(&stream);
139 videoSerial = serial;
140 ogg_stream_init(&theoraStream, serial);
142 theoraHeaderPage(&page, ti, tc, tsi);
144 }
else if (memcmp(packet.packet,
"fishead", 8) == 0) {
145 skeletonSerial = serial;
147 }
else if (memcmp(packet.packet,
"BBCD", 4) == 0) {
148 ogg_stream_clear(&stream);
151 }
else if (memcmp(packet.packet,
"\177FLAC", 5) == 0) {
152 ogg_stream_clear(&stream);
156 ogg_stream_clear(&stream);
160 ogg_stream_clear(&stream);
163 if (videoSerial == -1) {
167 if (audioSerial == -1) {
171 if (vi.channels != 2) {
175 if (ti.frame_width != 640 || ti.frame_height != 480) {
179 if (ti.fps_numerator == 30000 && ti.fps_denominator == 1001) {
181 }
else if (ti.fps_numerator == 60000 && ti.fps_denominator == 1001) {
184 throw MSXException(
"Video frame rate must be 59.94Hz or 29.97Hz");
191 if (ti.pixel_fmt != TH_PF_420) {
198 th_comment_clear(&tc);
205 th_comment_clear(&tc);
208 void OggReader::cleanup()
210 if (audioHeaders == 0) {
211 vorbis_dsp_clear(&vd);
212 vorbis_block_clear(&vb);
215 th_decode_free(theora);
217 vorbis_info_clear(&vi);
218 vorbis_comment_clear(&vc);
219 if (audioSerial != -1) {
220 ogg_stream_clear(&vorbisStream);
223 if (videoSerial != -1) {
224 ogg_stream_clear(&theoraStream);
227 ogg_sync_clear(&sync);
240 void OggReader::vorbisFoundPosition()
242 auto last = vorbisPos;
244 last -= audioFrag->length;
245 audioFrag->position = last;
249 if (last > currentSample) {
253 if (vorbisPos > currentSample) {
254 currentSample = vorbisPos;
259 void OggReader::vorbisHeaderPage(ogg_page* page)
261 ogg_stream_pagein(&vorbisStream, page);
266 int res = ogg_stream_packetout(&vorbisStream, &packet);
268 throw MSXException(
"error in vorbis stream");
272 if (audioHeaders == 0) {
277 if (packet.packetno <= 2) {
278 if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) {
279 throw MSXException(
"invalid vorbis header");
284 if (packet.packetno == 2) {
285 vorbis_synthesis_init(&vd, &vi) ;
286 vorbis_block_init(&vd, &vb);
291 void OggReader::theoraHeaderPage(ogg_page* page, th_info& ti, th_comment& tc,
294 ogg_stream_pagein(&theoraStream, page);
299 int res = ogg_stream_packetout(&theoraStream, &packet);
301 throw MSXException(
"error in vorbis stream");
310 if (packet.packetno <= 2) {
311 res = th_decode_headerin(&ti, &tc, &tsi, &packet);
313 throw MSXException(
"invalid theora header");
317 if (packet.packetno == 2) {
318 theora = th_decode_alloc(&ti, tsi);
320 granuleShift = ti.keyframe_granule_shift;
325 void OggReader::readVorbis(ogg_packet* packet)
328 if (
unlikely(packet->packetno <= 2)) {
332 if (state == FIND_LAST) {
333 if (packet->granulepos != -1) {
335 (
size_t(packet->granulepos) > currentSample)) {
336 currentSample = packet->granulepos;
341 }
else if (state == FIND_FIRST) {
342 if (packet->granulepos != -1 &&
344 currentSample = packet->granulepos;
348 }
else if (state == FIND_KEYFRAME) {
354 if (vorbis_synthesis(&vb, packet) != 0) {
358 vorbis_synthesis_blockin(&vd, &vb);
361 long decoded = vorbis_synthesis_pcmout(&vd, &pcm);
364 while (pos < decoded) {
366 if (recycleAudioList.empty()) {
367 auto audio = std::make_unique<AudioFragment>();
369 recycleAudioList.push_back(std::move(audio));
371 auto& audio = recycleAudioList.front();
372 if (audio->length == 0) {
373 audio->position = vorbisPos;
379 unsigned len = std::min<long>(decoded - pos,
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));
387 audio->length += len;
391 bool last = (decoded == pos && (packet->e_o_s ||
393 packet->granulepos != -1)));
397 currentSample += len;
401 audioList.push_back(recycleAudioList.pop_front());
407 if (packet->granulepos != -1) {
409 vorbisPos = packet->granulepos;
410 vorbisFoundPosition();
412 if (vorbisPos !=
size_t(packet->granulepos)) {
414 "vorbis audio out of sync, expected ",
415 vorbisPos,
", got ", packet->granulepos);
416 vorbisPos = packet->granulepos;
422 vorbis_synthesis_read(&vd, decoded);
425 size_t OggReader::frameNo(ogg_packet* packet)
const
427 if (packet->granulepos == -1) {
431 size_t intra = packet->granulepos & ((size_t(1) << granuleShift) - 1);
432 size_t key = packet->granulepos >> granuleShift;
436 void OggReader::readMetadata(th_comment& tc)
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=");
453 while (isspace(*p)) {
456 if (strncasecmp(p,
"chapter: ", 9) == 0) {
457 int chapter = atoi(p + 9);
461 size_t frame = atol(p);
463 chapters.emplace_back(chapter, frame);
465 }
else if (strncasecmp(p,
"stop: ", 6) == 0) {
466 size_t stopframe = atol(p + 6);
468 stopFrames.push_back(stopframe);
478 void OggReader::readTheora(ogg_packet* packet)
480 if (th_packet_isheader(packet)) {
484 size_t frameno = frameNo(packet);
488 if ((state != PLAYING) && (frameno ==
size_t(-1))) {
492 if (state == FIND_LAST) {
493 if ((currentFrame ==
size_t(-1)) || (currentFrame < frameno)) {
494 currentFrame = frameno;
498 }
else if (state == FIND_FIRST) {
499 if ((currentFrame ==
size_t(-1)) || (currentFrame > frameno)) {
500 currentFrame = frameno;
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;
510 }
else if (frameno > currentFrame) {
516 if ((keyFrame !=
size_t(-1)) && (frameno !=
size_t(-1)) &&
517 (frameno < keyFrame)) {
522 if (packet->bytes == 0 && frameList.empty()) {
528 keyFrame = size_t(-1);
530 int rc = th_decode_packetin(theora, packet,
nullptr);
533 if (frameList.empty()) {
535 "without preceding frame");
537 frameList.back()->length++;
541 cli.
printWarning(
"Theora error: not capable of reading this");
544 cli.
printWarning(
"Theora error: API not used correctly");
562 if (th_decode_ycbcr_out(theora, yuv) != 0) {
566 if ((frameno !=
size_t(-1)) && (frameno < currentFrame)) {
570 currentFrame = frameno + 1;
572 std::unique_ptr<Frame> frame;
573 if (recycleFrameList.empty()) {
574 frame = std::make_unique<Frame>(yuv);
576 frame = std::move(recycleFrameList.back());
577 recycleFrameList.pop_back();
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);
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)) {
594 frameno = last->no + last->length;
604 if (!frameList.empty() && (frameno !=
size_t(-1)) &&
605 (frameList[0]->no ==
size_t(-1))) {
607 frameno -= frm->length;
612 frameList.push_back(std::move(frame));
622 if (frameList.empty() || (frameList[0]->no ==
size_t(-1))) {
632 while (frameList.size() >= 3 && frameList[2]->no <= frameno) {
633 recycleFrameList.push_back(frameList.pop_front());
636 if (!frameList.empty() && frameList[0]->no > frameno) {
638 frame = frameList[0].get();
640 "Cannot find frame ", frameno,
" using ",
641 frame->
no,
" instead");
645 if ((frameList.size() >= 2) &&
646 ((frameno >= frameList[0]->no) &&
647 (frameno < frameList[1]->no))) {
648 frame = frameList[0].get();
652 if ((frameList.size() >= 3) &&
653 ((frameno >= frameList[1]->no) &&
654 (frameno < frameList[2]->no))) {
655 frame = frameList[1].get();
660 if (frameList.size() > (
size_t(2) << granuleShift)) {
676 void OggReader::recycleAudio(std::unique_ptr<AudioFragment> audio)
679 recycleAudioList.push_back(std::move(audio));
685 while (audioList.empty() ||
692 auto it =
begin(audioList);
695 if (audio->position + audio->length +
getSampleRate() <= sample) {
697 recycleAudio(std::move(*it));
698 it = audioList.erase(it);
699 }
else if (audio->position + audio->length <= sample) {
702 if (audio->position <= sample) {
711 if (it ==
end(audioList)) {
712 size_t size = audioList.size();
713 while (
size == audioList.size()) {
720 it =
begin(audioList);
725 bool OggReader::nextPacket()
731 int ret = ogg_stream_packetout(&vorbisStream, &packet);
735 }
else if (ret == -1) {
740 ret = ogg_stream_packetout(&theoraStream, &packet);
744 }
else if (ret == -1) {
749 if (!nextPage(&page)) {
753 int serial = ogg_page_serialno(&page);
754 if (serial == audioSerial) {
755 if (ogg_stream_pagein(&vorbisStream, &page)) {
758 }
else if (serial == videoSerial) {
759 if (ogg_stream_pagein(&theoraStream, &page)) {
762 }
else if (serial != skeletonSerial) {
764 serial,
" in ogg file");
770 bool OggReader::nextPage(ogg_page* page)
772 constexpr
size_t CHUNK = 4096;
775 while ((ret = ogg_sync_pageseek(&sync, page)) <= 0) {
781 if (fileSize - fileOffset >= CHUNK) {
783 }
else if (fileOffset < fileSize) {
784 chunk = fileSize - fileOffset;
789 char* buffer = ogg_sync_buffer(&sync,
long(chunk));
790 file.
read(buffer, chunk);
793 if (ogg_sync_wrote(&sync,
long(chunk)) == -1) {
794 cli.
printWarning(
"Internal error: ogg_sync_wrote failed");
801 size_t OggReader::bisection(
802 size_t frame,
size_t sample,
803 size_t maxOffset,
size_t maxSamples,
size_t maxFrames)
807 constexpr uint64_t SHIFT = 0x20000000ull;
809 uint64_t offsetA = 0, offsetB = maxOffset;
810 uint64_t sampleA = 0, sampleB = maxSamples;
811 uint64_t frameA = 1, frameB = maxFrames;
814 uint64_t ratio = (frame - frameA) * SHIFT / (frameB - frameA);
819 uint64_t frameOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
820 ratio = (sample - sampleA) * SHIFT / (sampleB - sampleA);
824 uint64_t sampleOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
825 auto offset =
std::min(sampleOffset, frameOffset);
829 ogg_sync_reset(&sync);
830 currentFrame = size_t(-1);
834 while (((currentFrame ==
size_t(-1)) ||
842 if (currentSample > sample || currentFrame > frame) {
844 sampleB = currentSample;
845 frameB = currentFrame;
847 currentFrame + 64 < frame) {
849 sampleA = currentSample;
850 frameA = currentFrame;
857 size_t OggReader::findOffset(
size_t frame,
size_t sample)
859 constexpr
size_t STEP = 32 * 1024;
867 auto offset = fileSize - 1;
878 ogg_sync_reset(&sync);
879 currentFrame = size_t(-1);
883 while (nextPacket()) {
889 if ((currentFrame !=
size_t(-1)) &&
895 totalFrames = currentFrame;
904 auto maxOffset = offset;
905 auto maxSamples = currentSample;
906 auto maxFrames = currentFrame;
908 if ((sample > maxSamples) || (frame > maxFrames)) {
913 offset = bisection(frame, sample, maxOffset, maxSamples, maxFrames);
918 ogg_sync_reset(&sync);
919 currentFrame = frame;
921 keyFrame = size_t(-1);
922 state = FIND_KEYFRAME;
924 while (currentSample == 0 && nextPacket()) {
930 if (keyFrame ==
one_of(
size_t(-1), frame)) {
934 return bisection(keyFrame, sample, maxOffset, maxSamples, maxFrames);
940 recycleFrameList.insert(
end(recycleFrameList),
941 std::move_iterator(
begin(frameList)),
942 std::move_iterator(
end (frameList)));
946 if (!recycleAudioList.empty()) {
947 recycleAudioList.front()->length = 0;
949 for (
auto& a : audioList) {
950 recycleAudio(std::move(a));
954 fileOffset = findOffset(frame, samples);
955 file.
seek(fileOffset);
957 ogg_sync_reset(&sync);
960 currentFrame = frame;
961 currentSample = samples;
963 vorbis_synthesis_restart(&vd);
976 return ((it !=
end(chapters)) && (it->first == chapterNo))