void TheoraDecoder::readNextPacket() { // First, let's get our frame if (_hasVideo) { while (!_videoTrack->endOfTrack()) { // theora is one in, one out... if (ogg_stream_packetout(&_theoraOut, &_oggPacket) > 0) { if (_videoTrack->decodePacket(_oggPacket)) break; } else if (_theoraOut.e_o_s || _fileStream->eos()) { // If we can't get any more frames, we're done. _videoTrack->setEndOfVideo(); } else { // Queue more data bufferData(); while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) queuePage(&_oggPage); } // Update audio if we can queueAudio(); } } // Then make sure we have enough audio buffered ensureAudioBufferSize(); }
void TheoraDecoder::ensureAudioBufferSize() { if (!_hasAudio) return; // Force at least some audio to be buffered while (_audioTrack->needsAudio()) { bufferData(); while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) queuePage(&_oggPage); bool queuedAudio = queueAudio(); if ((_vorbisOut.e_o_s || _fileStream->eos()) && !queuedAudio) { _audioTrack->setEndOfAudio(); break; } } }
bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { close(); _fileStream = stream; // start up Ogg stream synchronization layer ogg_sync_init(&_oggSync); // init supporting Vorbis structures needed in header parsing vorbis_info_init(&_vorbisInfo); vorbis_comment vorbisComment; vorbis_comment_init(&vorbisComment); // init supporting Theora structures needed in header parsing th_info theoraInfo; th_info_init(&theoraInfo); th_comment theoraComment; th_comment_init(&theoraComment); th_setup_info *theoraSetup = 0; uint theoraPackets = 0, vorbisPackets = 0; // Ogg file open; parse the headers // Only interested in Vorbis/Theora streams bool foundHeader = false; while (!foundHeader) { int ret = bufferData(); if (ret == 0) break; // FIXME: Shouldn't this error out? while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { ogg_stream_state test; // is this a mandated initial header? If not, stop parsing if (!ogg_page_bos(&_oggPage)) { // don't leak the page; get it into the appropriate stream queuePage(&_oggPage); foundHeader = true; break; } ogg_stream_init(&test, ogg_page_serialno(&_oggPage)); ogg_stream_pagein(&test, &_oggPage); ogg_stream_packetout(&test, &_oggPacket); // identify the codec: try theora if (theoraPackets == 0 && th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket) >= 0) { // it is theora memcpy(&_theoraOut, &test, sizeof(test)); theoraPackets = 1; _hasVideo = true; } else if (vorbisPackets == 0 && vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket) >= 0) { // it is vorbis memcpy(&_vorbisOut, &test, sizeof(test)); vorbisPackets = 1; _hasAudio = true; } else { // whatever it is, we don't care about it ogg_stream_clear(&test); } } // fall through to non-bos page parsing } // we're expecting more header packets. while ((theoraPackets && theoraPackets < 3) || (vorbisPackets && vorbisPackets < 3)) { int ret; // look for further theora headers while (theoraPackets && (theoraPackets < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) { if (ret < 0) error("Error parsing Theora stream headers; corrupt stream?"); if (!th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket)) error("Error parsing Theora stream headers; corrupt stream?"); theoraPackets++; } // look for more vorbis header packets while (vorbisPackets && (vorbisPackets < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) { if (ret < 0) error("Error parsing Vorbis stream headers; corrupt stream?"); if (vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket)) error("Error parsing Vorbis stream headers; corrupt stream?"); vorbisPackets++; if (vorbisPackets == 3) break; } // The header pages/packets will arrive before anything else we // care about, or the stream is not obeying spec if (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { queuePage(&_oggPage); // demux into the appropriate stream } else { ret = bufferData(); // someone needs more data if (ret == 0) error("End of file while searching for codec headers."); } } // And now we have it all. Initialize decoders next if (_hasVideo) { _videoTrack = new TheoraVideoTrack(getDefaultHighColorFormat(), theoraInfo, theoraSetup); addTrack(_videoTrack); } th_info_clear(&theoraInfo); th_comment_clear(&theoraComment); th_setup_free(theoraSetup); if (_hasAudio) { _audioTrack = new VorbisAudioTrack(_soundType, _vorbisInfo); // Get enough audio data to start us off while (!_audioTrack->hasAudio()) { // Queue more data bufferData(); while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) queuePage(&_oggPage); queueAudio(); } addTrack(_audioTrack); } vorbis_comment_clear(&vorbisComment); return true; }
bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { close(); _endOfAudio = false; _endOfVideo = false; _fileStream = stream; // start up Ogg stream synchronization layer ogg_sync_init(&_oggSync); // init supporting Vorbis structures needed in header parsing vorbis_info_init(&_vorbisInfo); vorbis_comment_init(&_vorbisComment); // init supporting Theora structures needed in header parsing th_comment_init(&_theoraComment); th_info_init(&_theoraInfo); // Ogg file open; parse the headers // Only interested in Vorbis/Theora streams bool foundHeader = false; while (!foundHeader) { int ret = bufferData(); if (ret == 0) break; while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { ogg_stream_state test; // is this a mandated initial header? If not, stop parsing if (!ogg_page_bos(&_oggPage)) { // don't leak the page; get it into the appropriate stream queuePage(&_oggPage); foundHeader = true; break; } ogg_stream_init(&test, ogg_page_serialno(&_oggPage)); ogg_stream_pagein(&test, &_oggPage); ogg_stream_packetout(&test, &_oggPacket); // identify the codec: try theora if (!_theoraPacket && th_decode_headerin(&_theoraInfo, &_theoraComment, &_theoraSetup, &_oggPacket) >= 0) { // it is theora memcpy(&_theoraOut, &test, sizeof(test)); _theoraPacket = 1; } else if (!_vorbisPacket && vorbis_synthesis_headerin(&_vorbisInfo, &_vorbisComment, &_oggPacket) >= 0) { // it is vorbis memcpy(&_vorbisOut, &test, sizeof(test)); _vorbisPacket = 1; } else { // whatever it is, we don't care about it ogg_stream_clear(&test); } } // fall through to non-bos page parsing } // we're expecting more header packets. while ((_theoraPacket && _theoraPacket < 3) || (_vorbisPacket && _vorbisPacket < 3)) { int ret; // look for further theora headers while (_theoraPacket && (_theoraPacket < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) { if (ret < 0) error("Error parsing Theora stream headers; corrupt stream?"); if (!th_decode_headerin(&_theoraInfo, &_theoraComment, &_theoraSetup, &_oggPacket)) error("Error parsing Theora stream headers; corrupt stream?"); _theoraPacket++; } // look for more vorbis header packets while (_vorbisPacket && (_vorbisPacket < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) { if (ret < 0) error("Error parsing Vorbis stream headers; corrupt stream?"); if (vorbis_synthesis_headerin(&_vorbisInfo, &_vorbisComment, &_oggPacket)) error("Error parsing Vorbis stream headers; corrupt stream?"); _vorbisPacket++; if (_vorbisPacket == 3) break; } // The header pages/packets will arrive before anything else we // care about, or the stream is not obeying spec if (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { queuePage(&_oggPage); // demux into the appropriate stream } else { ret = bufferData(); // someone needs more data if (ret == 0) error("End of file while searching for codec headers."); } } // and now we have it all. initialize decoders if (_theoraPacket) { _theoraDecode = th_decode_alloc(&_theoraInfo, _theoraSetup); debugN(1, "Ogg logical stream %lx is Theora %dx%d %.02f fps", _theoraOut.serialno, _theoraInfo.pic_width, _theoraInfo.pic_height, (double)_theoraInfo.fps_numerator / _theoraInfo.fps_denominator); switch (_theoraInfo.pixel_fmt) { case TH_PF_420: debug(1, " 4:2:0 video"); break; case TH_PF_422: debug(1, " 4:2:2 video"); break; case TH_PF_444: debug(1, " 4:4:4 video"); break; case TH_PF_RSVD: default: debug(1, " video\n (UNKNOWN Chroma sampling!)"); break; } if (_theoraInfo.pic_width != _theoraInfo.frame_width || _theoraInfo.pic_height != _theoraInfo.frame_height) debug(1, " Frame content is %dx%d with offset (%d,%d).", _theoraInfo.frame_width, _theoraInfo.frame_height, _theoraInfo.pic_x, _theoraInfo.pic_y); switch (_theoraInfo.colorspace){ case TH_CS_UNSPECIFIED: /* nothing to report */ break; case TH_CS_ITU_REC_470M: debug(1, " encoder specified ITU Rec 470M (NTSC) color."); break; case TH_CS_ITU_REC_470BG: debug(1, " encoder specified ITU Rec 470BG (PAL) color."); break; default: debug(1, "warning: encoder specified unknown colorspace (%d).", _theoraInfo.colorspace); break; } debug(1, "Encoded by %s", _theoraComment.vendor); if (_theoraComment.comments) { debug(1, "theora comment header:"); for (int i = 0; i < _theoraComment.comments; i++) { if (_theoraComment.user_comments[i]) { int len = _theoraComment.comment_lengths[i]; char *value = (char *)malloc(len + 1); if (value) { memcpy(value, _theoraComment.user_comments[i], len); value[len] = '\0'; debug(1, "\t%s", value); free(value); } } } } th_decode_ctl(_theoraDecode, TH_DECCTL_GET_PPLEVEL_MAX, &_ppLevelMax, sizeof(_ppLevelMax)); _ppLevel = _ppLevelMax; th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &_ppLevel, sizeof(_ppLevel)); _ppInc = 0; } else { // tear down the partial theora setup th_info_clear(&_theoraInfo); th_comment_clear(&_theoraComment); } th_setup_free(_theoraSetup); _theoraSetup = 0; if (_vorbisPacket) { vorbis_synthesis_init(&_vorbisDSP, &_vorbisInfo); vorbis_block_init(&_vorbisDSP, &_vorbisBlock); debug(3, "Ogg logical stream %lx is Vorbis %d channel %ld Hz audio.", _vorbisOut.serialno, _vorbisInfo.channels, _vorbisInfo.rate); _audStream = Audio::makeQueuingAudioStream(_vorbisInfo.rate, _vorbisInfo.channels); // Get enough audio data to start us off while (_audStream->numQueuedStreams() == 0) { // Queue more data bufferData(); while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) queuePage(&_oggPage); queueAudio(); } if (_audStream) g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, _audHandle, _audStream, -1, getVolume(), getBalance()); } else { // tear down the partial vorbis setup vorbis_info_clear(&_vorbisInfo); vorbis_comment_clear(&_vorbisComment); _endOfAudio = true; } _surface.create(_theoraInfo.frame_width, _theoraInfo.frame_height, g_system->getScreenFormat()); // Set up a display surface _displaySurface.pixels = _surface.getBasePtr(_theoraInfo.pic_x, _theoraInfo.pic_y); _displaySurface.w = _theoraInfo.pic_width; _displaySurface.h = _theoraInfo.pic_height; _displaySurface.format = _surface.format; _displaySurface.pitch = _surface.pitch; // Set the frame rate _frameRate = Common::Rational(_theoraInfo.fps_numerator, _theoraInfo.fps_denominator); return true; }
const Graphics::Surface *TheoraDecoder::decodeNextFrame() { // First, let's get our frame while (_theoraPacket) { // theora is one in, one out... if (ogg_stream_packetout(&_theoraOut, &_oggPacket) > 0) { if (_ppInc) { _ppLevel += _ppInc; th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &_ppLevel, sizeof(_ppLevel)); _ppInc = 0; } if (th_decode_packetin(_theoraDecode, &_oggPacket, NULL) == 0) { _curFrame++; // Convert YUV data to RGB data th_ycbcr_buffer yuv; th_decode_ycbcr_out(_theoraDecode, yuv); translateYUVtoRGBA(yuv); if (_curFrame == 0) _startTime = g_system->getMillis(); double time = th_granule_time(_theoraDecode, _oggPacket.granulepos); // We need to calculate when the next frame should be shown // This is all in floating point because that's what the Ogg code gives us // Ogg is a lossy container format, so it doesn't always list the time to the // next frame. In such cases, we need to calculate it ourselves. if (time == -1.0) _nextFrameStartTime += _frameRate.getInverse().toDouble(); else _nextFrameStartTime = time; // break out break; } } else { // If we can't get any more frames, we're done. if (_theoraOut.e_o_s || _fileStream->eos()) { _endOfVideo = true; break; } // Queue more data bufferData(); while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) queuePage(&_oggPage); } // Update audio if we can queueAudio(); } // Force at least some audio to be buffered // TODO: 5 is very arbitrary. We probably should do something like QuickTime does. while (!_endOfAudio && _audStream->numQueuedStreams() < 5) { bufferData(); while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) queuePage(&_oggPage); bool queuedAudio = queueAudio(); if ((_vorbisOut.e_o_s || _fileStream->eos()) && !queuedAudio) { _endOfAudio = true; break; } } return &_displaySurface; }