25 unsigned y_size = yuv[0].height * yuv[0].stride;
26 unsigned uv_size = yuv[1].height * yuv[1].stride;
29 buffer[0].data =
static_cast<unsigned char*
>(
32 buffer[1].data =
static_cast<unsigned char*
>(
35 buffer[2].data =
static_cast<unsigned char*
>(
55 keyFrame = size_t(-1);
62 th_setup_info* tsi =
nullptr;
68 vorbis_info_init(&vi);
69 vorbis_comment_init(&vc);
80 while ((audioHeaders || !theora) && nextPage(&page)) {
81 int serial = ogg_page_serialno(&page);
83 if (serial == audioSerial) {
84 vorbisHeaderPage(&page);
86 }
else if (serial == videoSerial) {
87 theoraHeaderPage(&page, ti, tc, tsi);
89 }
else if (serial == skeletonSerial) {
93 if (!ogg_page_bos(&page)) {
94 if (videoSerial == -1) {
98 if (audioSerial == -1) {
106 ogg_stream_state stream;
109 ogg_stream_init(&stream, serial);
110 ogg_stream_pagein(&stream, &page);
111 if (ogg_stream_packetout(&stream, &packet) <= 0) {
112 ogg_stream_clear(&stream);
116 if (packet.bytes < 8) {
117 ogg_stream_clear(&stream);
121 if (memcmp(packet.packet,
"\x01vorbis", 7) == 0) {
122 if (audioSerial != -1) {
123 ogg_stream_clear(&stream);
127 audioSerial = serial;
128 ogg_stream_init(&vorbisStream, serial);
130 vorbisHeaderPage(&page);
132 }
else if (memcmp(packet.packet,
"\x80theora", 7) == 0) {
133 if (videoSerial != -1) {
134 ogg_stream_clear(&stream);
138 videoSerial = serial;
139 ogg_stream_init(&theoraStream, serial);
141 theoraHeaderPage(&page, ti, tc, tsi);
143 }
else if (memcmp(packet.packet,
"fishead", 8) == 0) {
144 skeletonSerial = serial;
146 }
else if (memcmp(packet.packet,
"BBCD", 4) == 0) {
147 ogg_stream_clear(&stream);
150 }
else if (memcmp(packet.packet,
"\177FLAC", 5) == 0) {
151 ogg_stream_clear(&stream);
155 ogg_stream_clear(&stream);
159 ogg_stream_clear(&stream);
162 if (videoSerial == -1) {
166 if (audioSerial == -1) {
170 if (vi.channels != 2) {
174 if (ti.frame_width != 640 || ti.frame_height != 480) {
178 if (ti.fps_numerator == 30000 && ti.fps_denominator == 1001) {
180 }
else if (ti.fps_numerator == 60000 && ti.fps_denominator == 1001) {
183 throw MSXException(
"Video frame rate must be 59.94Hz or 29.97Hz");
190 if (ti.pixel_fmt != TH_PF_420) {
197 th_comment_clear(&tc);
204 th_comment_clear(&tc);
207 void OggReader::cleanup()
209 if (audioHeaders == 0) {
210 vorbis_dsp_clear(&vd);
211 vorbis_block_clear(&vb);
214 th_decode_free(theora);
216 vorbis_info_clear(&vi);
217 vorbis_comment_clear(&vc);
218 if (audioSerial != -1) {
219 ogg_stream_clear(&vorbisStream);
222 if (videoSerial != -1) {
223 ogg_stream_clear(&theoraStream);
226 ogg_sync_clear(&sync);
239 void OggReader::vorbisFoundPosition()
241 auto last = vorbisPos;
243 last -= audioFrag->length;
244 audioFrag->position = last;
248 if (last > currentSample) {
252 if (vorbisPos > currentSample) {
253 currentSample = vorbisPos;
258 void OggReader::vorbisHeaderPage(ogg_page* page)
260 ogg_stream_pagein(&vorbisStream, page);
265 int res = ogg_stream_packetout(&vorbisStream, &packet);
267 throw MSXException(
"error in vorbis stream");
271 if (audioHeaders == 0) {
276 if (packet.packetno <= 2) {
277 if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) {
278 throw MSXException(
"invalid vorbis header");
283 if (packet.packetno == 2) {
284 vorbis_synthesis_init(&vd, &vi) ;
285 vorbis_block_init(&vd, &vb);
290 void OggReader::theoraHeaderPage(ogg_page* page, th_info& ti, th_comment& tc,
293 ogg_stream_pagein(&theoraStream, page);
298 int res = ogg_stream_packetout(&theoraStream, &packet);
300 throw MSXException(
"error in vorbis stream");
309 if (packet.packetno <= 2) {
310 res = th_decode_headerin(&ti, &tc, &tsi, &packet);
312 throw MSXException(
"invalid theora header");
316 if (packet.packetno == 2) {
317 theora = th_decode_alloc(&ti, tsi);
319 granuleShift = ti.keyframe_granule_shift;
324 void OggReader::readVorbis(ogg_packet* packet)
327 if (
unlikely(packet->packetno <= 2)) {
331 if (state == FIND_LAST) {
332 if (packet->granulepos != -1) {
334 (
size_t(packet->granulepos) > currentSample)) {
335 currentSample = packet->granulepos;
340 }
else if (state == FIND_FIRST) {
341 if (packet->granulepos != -1 &&
343 currentSample = packet->granulepos;
347 }
else if (state == FIND_KEYFRAME) {
353 if (vorbis_synthesis(&vb, packet) != 0) {
357 vorbis_synthesis_blockin(&vd, &vb);
360 long decoded = vorbis_synthesis_pcmout(&vd, &pcm);
363 while (pos < decoded) {
365 if (recycleAudioList.empty()) {
366 auto audio = std::make_unique<AudioFragment>();
368 recycleAudioList.push_back(std::move(audio));
370 auto& audio = recycleAudioList.front();
371 if (audio->length == 0) {
372 audio->position = vorbisPos;
378 unsigned len = std::min<long>(decoded - pos,
381 memcpy(audio->pcm[0] + audio->length, pcm[0] + pos,
382 len *
sizeof(
float));
383 memcpy(audio->pcm[1] + audio->length, pcm[1] + pos,
384 len *
sizeof(
float));
386 audio->length += len;
390 bool last = (decoded == pos && (packet->e_o_s ||
392 packet->granulepos != -1)));
396 currentSample += len;
400 audioList.push_back(recycleAudioList.pop_front());
406 if (packet->granulepos != -1) {
408 vorbisPos = packet->granulepos;
409 vorbisFoundPosition();
411 if (vorbisPos !=
size_t(packet->granulepos)) {
413 "vorbis audio out of sync, expected ",
414 vorbisPos,
", got ", packet->granulepos);
415 vorbisPos = packet->granulepos;
421 vorbis_synthesis_read(&vd, decoded);
424 size_t OggReader::frameNo(ogg_packet* packet)
const
426 if (packet->granulepos == -1) {
430 size_t intra = packet->granulepos & ((size_t(1) << granuleShift) - 1);
431 size_t key = packet->granulepos >> granuleShift;
435 void OggReader::readMetadata(th_comment& tc)
437 char* metadata =
nullptr;
438 for (
auto i :
xrange(tc.comments)) {
439 if (!strncasecmp(tc.user_comments[i],
"location=",
440 strlen(
"location="))) {
441 metadata = tc.user_comments[i] + strlen(
"location=");
452 while (isspace(*p)) {
455 if (strncasecmp(p,
"chapter: ", 9) == 0) {
456 int chapter = atoi(p + 9);
460 size_t frame = atol(p);
462 chapters.emplace_back(ChapterFrame{chapter, frame});
464 }
else if (strncasecmp(p,
"stop: ", 6) == 0) {
465 size_t stopframe = atol(p + 6);
467 stopFrames.push_back(stopframe);
477 void OggReader::readTheora(ogg_packet* packet)
479 if (th_packet_isheader(packet)) {
483 size_t frameno = frameNo(packet);
487 if ((state != PLAYING) && (frameno ==
size_t(-1))) {
491 if (state == FIND_LAST) {
492 if ((currentFrame ==
size_t(-1)) || (currentFrame < frameno)) {
493 currentFrame = frameno;
497 }
else if (state == FIND_FIRST) {
498 if ((currentFrame ==
size_t(-1)) || (currentFrame > frameno)) {
499 currentFrame = frameno;
503 }
else if (state == FIND_KEYFRAME) {
504 if (frameno < currentFrame) {
505 keyFrame = packet->granulepos >> granuleShift;
506 }
else if (currentFrame == frameno) {
507 keyFrame = packet->granulepos >> granuleShift;
509 }
else if (frameno > currentFrame) {
515 if ((keyFrame !=
size_t(-1)) && (frameno !=
size_t(-1)) &&
516 (frameno < keyFrame)) {
521 if (packet->bytes == 0 && frameList.empty()) {
527 keyFrame = size_t(-1);
529 int rc = th_decode_packetin(theora, packet,
nullptr);
532 if (frameList.empty()) {
534 "without preceding frame");
536 frameList.back()->length++;
540 cli.
printWarning(
"Theora error: not capable of reading this");
543 cli.
printWarning(
"Theora error: API not used correctly");
561 if (th_decode_ycbcr_out(theora, yuv) != 0) {
565 if ((frameno !=
size_t(-1)) && (frameno < currentFrame)) {
569 currentFrame = frameno + 1;
571 std::unique_ptr<Frame> frame;
572 if (recycleFrameList.empty()) {
573 frame = std::make_unique<Frame>(yuv);
575 frame = std::move(recycleFrameList.back());
576 recycleFrameList.pop_back();
579 int y_size = yuv[0].height * yuv[0].stride;
580 int uv_size = yuv[1].height * yuv[1].stride;
581 memcpy(frame->buffer[0].data, yuv[0].data, y_size);
582 memcpy(frame->buffer[1].data, yuv[1].data, uv_size);
583 memcpy(frame->buffer[2].data, yuv[2].data, uv_size);
588 Frame* last = frameList.empty() ? nullptr : frameList.back().get();
589 if (last && (last->no !=
size_t(-1))) {
590 if (frameno !=
one_of(
size_t(-1), last->no + last->length)) {
593 frameno = last->no + last->length;
603 if (!frameList.empty() && (frameno !=
size_t(-1)) &&
604 (frameList[0]->no ==
size_t(-1))) {
606 frameno -= frm->length;
611 frameList.push_back(std::move(frame));
621 if (frameList.empty() || (frameList[0]->no ==
size_t(-1))) {
631 while (frameList.size() >= 3 && frameList[2]->no <= frameno) {
632 recycleFrameList.push_back(frameList.pop_front());
635 if (!frameList.empty() && frameList[0]->no > frameno) {
637 frame = frameList[0].get();
639 "Cannot find frame ", frameno,
" using ",
640 frame->
no,
" instead");
644 if ((frameList.size() >= 2) &&
645 ((frameno >= frameList[0]->no) &&
646 (frameno < frameList[1]->no))) {
647 frame = frameList[0].get();
651 if ((frameList.size() >= 3) &&
652 ((frameno >= frameList[1]->no) &&
653 (frameno < frameList[2]->no))) {
654 frame = frameList[1].get();
659 if (frameList.size() > (
size_t(2) << granuleShift)) {
675 void OggReader::recycleAudio(std::unique_ptr<AudioFragment> audio)
678 recycleAudioList.push_back(std::move(audio));
684 while (audioList.empty() ||
691 auto it =
begin(audioList);
694 if (audio->position + audio->length +
getSampleRate() <= sample) {
696 recycleAudio(std::move(*it));
697 it = audioList.erase(it);
698 }
else if (audio->position + audio->length <= sample) {
701 if (audio->position <= sample) {
710 if (it ==
end(audioList)) {
711 size_t size = audioList.size();
712 while (
size == audioList.size()) {
719 it =
begin(audioList);
724 bool OggReader::nextPacket()
730 int ret = ogg_stream_packetout(&vorbisStream, &packet);
734 }
else if (ret == -1) {
739 ret = ogg_stream_packetout(&theoraStream, &packet);
743 }
else if (ret == -1) {
748 if (!nextPage(&page)) {
752 int serial = ogg_page_serialno(&page);
753 if (serial == audioSerial) {
754 if (ogg_stream_pagein(&vorbisStream, &page)) {
757 }
else if (serial == videoSerial) {
758 if (ogg_stream_pagein(&theoraStream, &page)) {
761 }
else if (serial != skeletonSerial) {
763 serial,
" in ogg file");
769 bool OggReader::nextPage(ogg_page* page)
771 constexpr
size_t CHUNK = 4096;
774 while ((ret = ogg_sync_pageseek(&sync, page)) <= 0) {
780 if (fileSize - fileOffset >= CHUNK) {
782 }
else if (fileOffset < fileSize) {
783 chunk = fileSize - fileOffset;
788 char* buffer = ogg_sync_buffer(&sync,
long(chunk));
789 file.
read(buffer, chunk);
792 if (ogg_sync_wrote(&sync,
long(chunk)) == -1) {
793 cli.
printWarning(
"Internal error: ogg_sync_wrote failed");
800 size_t OggReader::bisection(
801 size_t frame,
size_t sample,
802 size_t maxOffset,
size_t maxSamples,
size_t maxFrames)
806 constexpr uint64_t SHIFT = 0x20000000ull;
808 uint64_t offsetA = 0, offsetB = maxOffset;
809 uint64_t sampleA = 0, sampleB = maxSamples;
810 uint64_t frameA = 1, frameB = maxFrames;
813 uint64_t ratio = (frame - frameA) * SHIFT / (frameB - frameA);
818 uint64_t frameOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
819 ratio = (sample - sampleA) * SHIFT / (sampleB - sampleA);
823 uint64_t sampleOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA;
824 auto offset =
std::min(sampleOffset, frameOffset);
828 ogg_sync_reset(&sync);
829 currentFrame = size_t(-1);
833 while (((currentFrame ==
size_t(-1)) ||
841 if (currentSample > sample || currentFrame > frame) {
843 sampleB = currentSample;
844 frameB = currentFrame;
846 currentFrame + 64 < frame) {
848 sampleA = currentSample;
849 frameA = currentFrame;
856 size_t OggReader::findOffset(
size_t frame,
size_t sample)
858 constexpr
size_t STEP = 32 * 1024;
866 auto offset = fileSize - 1;
877 ogg_sync_reset(&sync);
878 currentFrame = size_t(-1);
882 while (nextPacket()) {
888 if ((currentFrame !=
size_t(-1)) &&
894 totalFrames = currentFrame;
903 auto maxOffset = offset;
904 auto maxSamples = currentSample;
905 auto maxFrames = currentFrame;
907 if ((sample > maxSamples) || (frame > maxFrames)) {
912 offset = bisection(frame, sample, maxOffset, maxSamples, maxFrames);
917 ogg_sync_reset(&sync);
918 currentFrame = frame;
920 keyFrame = size_t(-1);
921 state = FIND_KEYFRAME;
923 while (currentSample == 0 && nextPacket()) {
929 if (keyFrame ==
one_of(
size_t(-1), frame)) {
933 return bisection(keyFrame, sample, maxOffset, maxSamples, maxFrames);
939 recycleFrameList.insert(
end(recycleFrameList),
940 std::move_iterator(
begin(frameList)),
941 std::move_iterator(
end (frameList)));
945 if (!recycleAudioList.empty()) {
946 recycleAudioList.front()->length = 0;
948 for (
auto& a : audioList) {
949 recycleAudio(std::move(a));
953 fileOffset = findOffset(frame, samples);
954 file.
seek(fileOffset);
956 ogg_sync_reset(&sync);
959 currentFrame = frame;
960 currentSample = samples;
962 vorbis_synthesis_restart(&vd);
975 return ((it !=
end(chapters)) && (it->chapter == chapterNo))
void printWarning(std::string_view message)
void seek(size_t pos)
Move read/write pointer to the specified position.
void read(void *buffer, size_t num)
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)
OggReader(const OggReader &)=delete
unsigned getSampleRate() const
size_t getChapter(int chapterNo) const
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...
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
void * mallocAligned(size_t alignment, size_t size)
void convert(const th_ycbcr_buffer &input, RawFrame &output)
This file implemented 3 utility functions:
constexpr const char *const filename
bool binary_search(ForwardRange &&range, const T &value)
void sort(RandomAccessRange &&range)
auto lower_bound(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
size_t size(std::string_view utf8)
constexpr auto reverse(Range &&range)
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)