24 unsigned y_size = yuv[0].height * yuv[0].stride;
25 unsigned uv_size = yuv[1].height * yuv[1].stride;
28 buffer[0].data =
static_cast<unsigned char*
>(
31 buffer[1].data =
static_cast<unsigned char*
>(
34 buffer[2].data =
static_cast<unsigned char*
>(
49 , fileSize(file.getSize())
53 th_setup_info* tsi =
nullptr;
58 vorbis_info_init(&vi);
59 vorbis_comment_init(&vc);
66 while ((audioHeaders || !theora) && nextPage(&page)) {
67 int serial = ogg_page_serialno(&page);
69 if (serial == audioSerial) {
70 vorbisHeaderPage(&page);
72 }
else if (serial == videoSerial) {
73 theoraHeaderPage(&page, ti, tc, tsi);
75 }
else if (serial == skeletonSerial) {
79 if (!ogg_page_bos(&page)) {
80 if (videoSerial == -1) {
84 if (audioSerial == -1) {
92 ogg_stream_state stream;
95 ogg_stream_init(&stream, serial);
96 ogg_stream_pagein(&stream, &page);
97 if (ogg_stream_packetout(&stream, &packet) <= 0) {
98 ogg_stream_clear(&stream);
102 if (packet.bytes < 8) {
103 ogg_stream_clear(&stream);
107 if (
ranges::equal(std::span{packet.packet, 7}, std::array<uint8_t, 7>{1,
'v',
'o',
'r',
'b',
'i',
's'})) {
108 if (audioSerial != -1) {
109 ogg_stream_clear(&stream);
113 audioSerial = serial;
114 ogg_stream_init(&vorbisStream, serial);
116 vorbisHeaderPage(&page);
118 }
else if (
ranges::equal(std::span{packet.packet, 7}, std::array<uint8_t, 7>{128,
't',
'h',
'e',
'o',
'r',
'a'})) {
119 if (videoSerial != -1) {
120 ogg_stream_clear(&stream);
124 videoSerial = serial;
125 ogg_stream_init(&theoraStream, serial);
127 theoraHeaderPage(&page, ti, tc, tsi);
129 }
else if (
ranges::equal(std::span{packet.packet, 8}, std::array<uint8_t, 8>{
'f',
'i',
's',
'h',
'e',
'a',
'd', 0})) {
130 skeletonSerial = serial;
132 }
else if (
ranges::equal(std::span{packet.packet, 4}, std::array<uint8_t, 4>{
'B',
'B',
'C',
'D'})) {
133 ogg_stream_clear(&stream);
136 }
else if (
ranges::equal(std::span{packet.packet, 5}, std::array<uint8_t, 5>{127,
'F',
'L',
'A',
'C'})) {
137 ogg_stream_clear(&stream);
141 ogg_stream_clear(&stream);
145 ogg_stream_clear(&stream);
148 if (videoSerial == -1) {
152 if (audioSerial == -1) {
156 if (vi.channels != 2) {
160 if (ti.frame_width != 640 || ti.frame_height != 480) {
164 if (ti.fps_numerator == 30000 && ti.fps_denominator == 1001) {
166 }
else if (ti.fps_numerator == 60000 && ti.fps_denominator == 1001) {
169 throw MSXException(
"Video frame rate must be 59.94Hz or 29.97Hz");
176 if (ti.pixel_fmt != TH_PF_420) {
182 th_comment_clear(&tc);
189 th_comment_clear(&tc);
192void OggReader::cleanup()
194 if (audioHeaders == 0) {
195 vorbis_dsp_clear(&vd);
196 vorbis_block_clear(&vb);
199 th_decode_free(theora);
201 vorbis_info_clear(&vi);
202 vorbis_comment_clear(&vc);
203 if (audioSerial != -1) {
204 ogg_stream_clear(&vorbisStream);
207 if (videoSerial != -1) {
208 ogg_stream_clear(&theoraStream);
211 ogg_sync_clear(&sync);
224void OggReader::vorbisFoundPosition()
226 auto last = vorbisPos;
227 for (
const auto& audioFrag :
view::reverse(audioList)) {
228 last -= audioFrag->length;
229 audioFrag->position = last;
233 if (last > currentSample) {
237 if (vorbisPos > currentSample) {
238 currentSample = vorbisPos;
243void OggReader::vorbisHeaderPage(ogg_page* page)
245 ogg_stream_pagein(&vorbisStream, page);
250 int res = ogg_stream_packetout(&vorbisStream, &packet);
252 throw MSXException(
"error in vorbis stream");
256 if (audioHeaders == 0) {
261 if (packet.packetno <= 2) {
262 if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) {
263 throw MSXException(
"invalid vorbis header");
268 if (packet.packetno == 2) {
269 vorbis_synthesis_init(&vd, &vi) ;
270 vorbis_block_init(&vd, &vb);
275void OggReader::theoraHeaderPage(ogg_page* page, th_info& ti, th_comment& tc,
278 ogg_stream_pagein(&theoraStream, page);
283 int res = ogg_stream_packetout(&theoraStream, &packet);
285 throw MSXException(
"error in vorbis stream");
294 if (packet.packetno <= 2) {
295 res = th_decode_headerin(&ti, &tc, &tsi, &packet);
297 throw MSXException(
"invalid theora header");
301 if (packet.packetno == 2) {
302 theora = th_decode_alloc(&ti, tsi);
304 granuleShift = ti.keyframe_granule_shift;
309void OggReader::readVorbis(ogg_packet* packet)
312 if (packet->packetno <= 2) [[unlikely]] {
316 if (state == FIND_LAST) {
317 if (packet->granulepos != -1) {
319 (
size_t(packet->granulepos) > currentSample)) {
320 currentSample = packet->granulepos;
325 }
else if (state == FIND_FIRST) {
326 if (packet->granulepos != -1 &&
328 currentSample = packet->granulepos;
332 }
else if (state == FIND_KEYFRAME) {
338 if (vorbis_synthesis(&vb, packet) != 0) {
342 vorbis_synthesis_blockin(&vd, &vb);
345 int decoded = vorbis_synthesis_pcmout(&vd, &pcm);
348 while (pos < decoded) {
350 if (recycleAudioList.empty()) {
351 auto audio = std::make_unique<AudioFragment>();
353 recycleAudioList.push_back(std::move(audio));
355 auto& audio = recycleAudioList.front();
356 if (audio->length == 0) {
357 audio->position = vorbisPos;
363 auto len = std::min(decoded - pos,
366 ranges::copy(std::span{&pcm[0][pos], size_t(len)}, &audio->pcm[0][audio->length]);
367 ranges::copy(std::span{&pcm[1][pos], size_t(len)}, &audio->pcm[1][audio->length]);
368 audio->length += len;
372 bool last = (decoded == pos && (packet->e_o_s ||
374 packet->granulepos != -1)));
378 currentSample += len;
382 audioList.push_back(recycleAudioList.pop_front());
388 if (packet->granulepos != -1) {
390 vorbisPos = packet->granulepos;
391 vorbisFoundPosition();
393 if (vorbisPos !=
size_t(packet->granulepos)) {
395 "vorbis audio out of sync, expected ",
396 vorbisPos,
", got ", packet->granulepos);
397 vorbisPos = packet->granulepos;
403 vorbis_synthesis_read(&vd, decoded);
406size_t OggReader::frameNo(
const ogg_packet* packet)
const
408 if (packet->granulepos == -1) {
412 size_t intra = packet->granulepos & ((size_t(1) << granuleShift) - 1);
413 size_t key = packet->granulepos >> granuleShift;
417void OggReader::readMetadata(th_comment& tc)
419 char* metadata =
nullptr;
420 for (
auto i :
xrange(tc.comments)) {
421 if (!strncasecmp(tc.user_comments[i],
"location=",
422 strlen(
"location="))) {
423 metadata = tc.user_comments[i] + strlen(
"location=");
434 while (isspace(*p)) {
437 if (strncasecmp(p,
"chapter: ", 9) == 0) {
438 int chapter = atoi(p + 9);
442 size_t frame = atol(p);
444 chapters.emplace_back(ChapterFrame{chapter, frame});
446 }
else if (strncasecmp(p,
"stop: ", 6) == 0) {
459void OggReader::readTheora(ogg_packet* packet)
461 if (th_packet_isheader(packet)) {
465 size_t frameno = frameNo(packet);
469 if ((state != PLAYING) && (frameno ==
size_t(-1))) {
473 if (state == FIND_LAST) {
474 if ((currentFrame ==
size_t(-1)) || (currentFrame < frameno)) {
475 currentFrame = frameno;
479 }
else if (state == FIND_FIRST) {
480 if ((currentFrame ==
size_t(-1)) || (currentFrame > frameno)) {
481 currentFrame = frameno;
485 }
else if (state == FIND_KEYFRAME) {
486 if (frameno < currentFrame) {
487 keyFrame = packet->granulepos >> granuleShift;
488 }
else if (currentFrame == frameno) {
489 keyFrame = packet->granulepos >> granuleShift;
491 }
else if (frameno > currentFrame) {
497 if ((keyFrame !=
size_t(-1)) && (frameno !=
size_t(-1)) &&
498 (frameno < keyFrame)) {
503 if (packet->bytes == 0 && frameList.empty()) {
509 keyFrame = size_t(-1);
511 int rc = th_decode_packetin(theora, packet,
nullptr);
514 if (frameList.empty()) {
516 "without preceding frame");
518 frameList.back()->length++;
522 cli.
printWarning(
"Theora error: not capable of reading this");
525 cli.
printWarning(
"Theora error: API not used correctly");
543 if (th_decode_ycbcr_out(theora, yuv) != 0) {
547 if ((frameno !=
size_t(-1)) && (frameno < currentFrame)) {
551 currentFrame = frameno + 1;
553 std::unique_ptr<Frame> frame;
554 if (recycleFrameList.empty()) {
555 frame = std::make_unique<Frame>(yuv);
557 frame = std::move(recycleFrameList.back());
558 recycleFrameList.pop_back();
561 size_t y_size = yuv[0].height * size_t(yuv[0].stride);
562 size_t uv_size = yuv[1].height * size_t(yuv[1].stride);
563 ranges::copy(std::span{yuv[0].data, y_size}, frame->buffer[0].data);
564 ranges::copy(std::span{yuv[1].data, uv_size}, frame->buffer[1].data);
565 ranges::copy(std::span{yuv[2].data, uv_size}, frame->buffer[2].data);
570 Frame* last = frameList.empty() ? nullptr : frameList.back().get();
571 if (last && (last->no !=
size_t(-1))) {
572 if (frameno !=
one_of(
size_t(-1), last->no + last->length)) {
575 frameno = last->no + last->length;
585 if (!frameList.empty() && (frameno !=
size_t(-1)) &&
586 (frameList[0]->no ==
size_t(-1))) {
588 frameno -= frm->length;
593 frameList.push_back(std::move(frame));
603 if (frameList.empty() || (frameList[0]->no ==
size_t(-1))) {
613 while (frameList.size() >= 3 && frameList[2]->no <= frameno) {
614 recycleFrameList.push_back(frameList.pop_front());
617 if (!frameList.empty() && frameList[0]->no > frameno) {
619 frame = frameList[0].get();
621 "Cannot find frame ", frameno,
" using ",
622 frame->
no,
" instead");
626 if ((frameList.size() >= 2) &&
627 ((frameno >= frameList[0]->no) &&
628 (frameno < frameList[1]->no))) {
629 frame = frameList[0].get();
633 if ((frameList.size() >= 3) &&
634 ((frameno >= frameList[1]->no) &&
635 (frameno < frameList[2]->no))) {
636 frame = frameList[1].get();
641 if (frameList.size() > (
size_t(2) << granuleShift)) {
657void OggReader::recycleAudio(std::unique_ptr<AudioFragment> audio)
660 recycleAudioList.push_back(std::move(audio));
666 while (audioList.empty() ||
673 auto it =
begin(audioList);
675 const auto& audio = *it;
676 if (audio->position + audio->length +
getSampleRate() <= sample) {
678 recycleAudio(std::move(*it));
679 it = audioList.erase(it);
680 }
else if (audio->position + audio->length <= sample) {
683 if (audio->position <= sample) {
692 if (it ==
end(audioList)) {
693 size_t size = audioList.size();
694 while (size == audioList.size()) {
701 it =
begin(audioList);
706bool OggReader::nextPacket()
712 int ret = ogg_stream_packetout(&vorbisStream, &packet);
716 }
else if (ret == -1) {
721 ret = ogg_stream_packetout(&theoraStream, &packet);
725 }
else if (ret == -1) {
730 if (!nextPage(&page)) {
734 int serial = ogg_page_serialno(&page);
735 if (serial == audioSerial) {
736 if (ogg_stream_pagein(&vorbisStream, &page)) {
739 }
else if (serial == videoSerial) {
740 if (ogg_stream_pagein(&theoraStream, &page)) {
743 }
else if (serial != skeletonSerial) {
745 serial,
" in ogg file");
751bool OggReader::nextPage(ogg_page* page)
753 static constexpr size_t CHUNK = 4096;
756 while ((ret = ogg_sync_pageseek(&sync, page)) <= 0) {
762 if (fileSize - fileOffset >= CHUNK) {
764 }
else if (fileOffset < fileSize) {
765 chunk = fileSize - fileOffset;
770 char* buffer = ogg_sync_buffer(&sync,
long(chunk));
771 file.
read(std::span{buffer, chunk});
774 if (ogg_sync_wrote(&sync,
long(chunk)) == -1) {
775 cli.
printWarning(
"Internal error: ogg_sync_wrote failed");
782size_t OggReader::bisection(
783 size_t frame,
size_t sample,
784 size_t maxOffset,
size_t maxSamples,
size_t maxFrames)
788 constexpr uint64_t SHIFT = 0x20000000ULL;
790 uint64_t offsetA = 0, offsetB = maxOffset;
791 uint64_t sampleA = 0, sampleB = maxSamples;
792 uint64_t frameA = 1, frameB = maxFrames;
795 uint64_t ratio = (frame - frameA) * SHIFT / (frameB - frameA);
800 uint64_t frameOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
801 ratio = (sample - sampleA) * SHIFT / (sampleB - sampleA);
805 uint64_t sampleOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
806 auto offset = std::min(sampleOffset, frameOffset);
810 ogg_sync_reset(&sync);
811 currentFrame = size_t(-1);
815 while (((currentFrame ==
size_t(-1)) ||
823 if (currentSample > sample || currentFrame > frame) {
825 sampleB = currentSample;
826 frameB = currentFrame;
828 currentFrame + 64 < frame) {
830 sampleA = currentSample;
831 frameA = currentFrame;
838size_t OggReader::findOffset(
size_t frame,
size_t sample)
840 static constexpr size_t STEP = 32 * 1024;
848 auto offset = fileSize - 1;
859 ogg_sync_reset(&sync);
860 currentFrame = size_t(-1);
864 while (nextPacket()) {
870 if ((currentFrame !=
size_t(-1)) &&
876 totalFrames = currentFrame;
885 auto maxOffset = offset;
886 auto maxSamples = currentSample;
887 auto maxFrames = currentFrame;
889 if ((sample > maxSamples) || (frame > maxFrames)) {
894 offset = bisection(frame, sample, maxOffset, maxSamples, maxFrames);
899 ogg_sync_reset(&sync);
900 currentFrame = frame;
902 keyFrame = size_t(-1);
903 state = FIND_KEYFRAME;
905 while (currentSample == 0 && nextPacket()) {
911 if (keyFrame ==
one_of(
size_t(-1), frame)) {
915 return bisection(keyFrame, sample, maxOffset, maxSamples, maxFrames);
921 recycleFrameList.insert(
end(recycleFrameList),
922 std::move_iterator(
begin(frameList)),
923 std::move_iterator(
end (frameList)));
927 if (!recycleAudioList.empty()) {
928 recycleAudioList.front()->length = 0;
930 for (
auto& a : audioList) {
931 recycleAudio(std::move(a));
935 fileOffset = findOffset(frame, samples);
936 file.
seek(fileOffset);
938 ogg_sync_reset(&sync);
941 currentFrame = frame;
942 currentSample = samples;
944 vorbis_synthesis_restart(&vd);
956 auto c =
binary_find(chapters, chapterNo, {}, &ChapterFrame::chapter);
957 return c ? c->frame : 0;
void printWarning(std::string_view message)
void seek(size_t pos)
Move read/write pointer to the specified position.
void read(std::span< uint8_t > buffer)
Read from file.
size_t getSize()
Returns the size of this file.
This class represents a filename.
bool stopFrame(size_t frame) const
void getFrameNo(RawFrame &frame, size_t frameno)
const AudioFragment * getAudio(size_t sample)
unsigned getSampleRate() const
size_t getChapter(int chapterNo) const
OggReader(const Filename &filename, CliComm &cli)
bool seek(size_t frame, size_t sample)
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
void * mallocAligned(size_t alignment, size_t size)
void convert(const th_ycbcr_buffer &input, RawFrame &output)
This file implemented 3 utility functions:
bool binary_search(ForwardRange &&range, const T &value)
constexpr bool equal(InputRange1 &&range1, InputRange2 &&range2, Pred pred={}, Proj1 proj1={}, Proj2 proj2={})
constexpr auto copy(InputRange &&range, OutputIter out)
constexpr void sort(RandomAccessRange &&range)
constexpr auto reverse(Range &&range)
auto * binary_find(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
static constexpr size_t UNKNOWN_POS
static constexpr unsigned MAX_SAMPLES
Frame(const th_ycbcr_buffer &yuv)
constexpr auto xrange(T e)
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)