diff options
-rw-r--r-- | engines/sword25/fmv/movieplayer.cpp | 1 | ||||
-rw-r--r-- | video/theora_decoder.cpp | 610 | ||||
-rw-r--r-- | video/theora_decoder.h | 129 |
3 files changed, 339 insertions, 401 deletions
diff --git a/engines/sword25/fmv/movieplayer.cpp b/engines/sword25/fmv/movieplayer.cpp index 9ee13b4b6d..a95532ec65 100644 --- a/engines/sword25/fmv/movieplayer.cpp +++ b/engines/sword25/fmv/movieplayer.cpp @@ -61,6 +61,7 @@ bool MoviePlayer::loadMovie(const Common::String &filename, uint z) { // Get the file and load it into the decoder Common::SeekableReadStream *in = Kernel::getInstance()->getPackage()->getStream(filename); _decoder.loadStream(in); + _decoder.start(); GraphicEngine *pGfx = Kernel::getInstance()->getGfx(); diff --git a/video/theora_decoder.cpp b/video/theora_decoder.cpp index f3d9cad096..76007eadff 100644 --- a/video/theora_decoder.cpp +++ b/video/theora_decoder.cpp @@ -36,72 +36,32 @@ #include "video/theora_decoder.h" -#include "common/debug.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" #include "common/stream.h" #include "common/system.h" #include "common/textconsole.h" #include "common/util.h" +#include "graphics/pixelformat.h" #include "graphics/yuv_to_rgb.h" -#include "audio/decoders/raw.h" namespace Video { -#define AUDIOFD_FRAGSIZE 10240 - -static double rint(double v) { - return floor(v + 0.5); -} - -TheoraDecoder::TheoraDecoder(Audio::Mixer::SoundType soundType) { +TheoraDecoder::TheoraDecoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) { _fileStream = 0; - _theoraPacket = 0; - _vorbisPacket = 0; - _theoraDecode = 0; - _theoraSetup = 0; - _nextFrameStartTime = 0.0; - - _soundType = soundType; - _audStream = 0; - _audHandle = new Audio::SoundHandle(); - - ogg_sync_init(&_oggSync); - - _curFrame = -1; - _audiobuf = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t)); - - reset(); + _videoTrack = 0; + _audioTrack = 0; + _hasVideo = _hasAudio = false; } TheoraDecoder::~TheoraDecoder() { close(); - delete _fileStream; - delete _audHandle; - free(_audiobuf); -} - -void TheoraDecoder::queuePage(ogg_page *page) { - if (_theoraPacket) - ogg_stream_pagein(&_theoraOut, page); - - if (_vorbisPacket) - ogg_stream_pagein(&_vorbisOut, page); -} - -int TheoraDecoder::bufferData() { - char *buffer = ogg_sync_buffer(&_oggSync, 4096); - int bytes = _fileStream->read(buffer, 4096); - - ogg_sync_wrote(&_oggSync, bytes); - - return bytes; } bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { close(); - _endOfAudio = false; - _endOfVideo = false; _fileStream = stream; // start up Ogg stream synchronization layer @@ -109,11 +69,17 @@ bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { // init supporting Vorbis structures needed in header parsing vorbis_info_init(&_vorbisInfo); - vorbis_comment_init(&_vorbisComment); + vorbis_comment vorbisComment; + vorbis_comment_init(&vorbisComment); // init supporting Theora structures needed in header parsing - th_comment_init(&_theoraComment); - th_info_init(&_theoraInfo); + 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 @@ -122,7 +88,7 @@ bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { int ret = bufferData(); if (ret == 0) - break; + break; // FIXME: Shouldn't this error out? while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { ogg_stream_state test; @@ -140,14 +106,16 @@ bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { ogg_stream_packetout(&test, &_oggPacket); // identify the codec: try theora - if (!_theoraPacket && th_decode_headerin(&_theoraInfo, &_theoraComment, &_theoraSetup, &_oggPacket) >= 0) { + if (theoraPackets == 0 && 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) { + theoraPackets = 1; + _hasVideo = true; + } else if (vorbisPackets == 0 && vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket) >= 0) { // it is vorbis memcpy(&_vorbisOut, &test, sizeof(test)); - _vorbisPacket = 1; + vorbisPackets = 1; + _hasAudio = true; } else { // whatever it is, we don't care about it ogg_stream_clear(&test); @@ -157,31 +125,31 @@ bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { } // we're expecting more header packets. - while ((_theoraPacket && _theoraPacket < 3) || (_vorbisPacket && _vorbisPacket < 3)) { + while ((theoraPackets && theoraPackets < 3) || (vorbisPackets && vorbisPackets < 3)) { int ret; // look for further theora headers - while (_theoraPacket && (_theoraPacket < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) { + 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)) + if (!th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket)) error("Error parsing Theora stream headers; corrupt stream?"); - _theoraPacket++; + theoraPackets++; } // look for more vorbis header packets - while (_vorbisPacket && (_vorbisPacket < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) { + 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)) + if (vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket)) error("Error parsing Vorbis stream headers; corrupt stream?"); - _vorbisPacket++; + vorbisPackets++; - if (_vorbisPacket == 3) + if (vorbisPackets == 3) break; } @@ -198,88 +166,21 @@ bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { } } - // 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); + // And now we have it all. Initialize decoders next + if (_hasVideo) { + _videoTrack = new TheoraVideoTrack(getDefaultHighColorFormat(), theoraInfo, theoraSetup); + addTrack(_videoTrack); } - 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); + th_info_clear(&theoraInfo); + th_comment_clear(&theoraComment); + th_setup_free(theoraSetup); - _audStream = Audio::makeQueuingAudioStream(_vorbisInfo.rate, _vorbisInfo.channels); + if (_hasAudio) { + _audioTrack = new VorbisAudioTrack(_soundType, _vorbisInfo); // Get enough audio data to start us off - while (_audStream->numQueuedStreams() == 0) { + while (!_audioTrack->hasAudio()) { // Queue more data bufferData(); while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) @@ -288,276 +189,299 @@ bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { 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; + addTrack(_audioTrack); } - _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); + vorbis_comment_clear(&vorbisComment); return true; } void TheoraDecoder::close() { - if (_vorbisPacket) { - ogg_stream_clear(&_vorbisOut); - vorbis_block_clear(&_vorbisBlock); - vorbis_dsp_clear(&_vorbisDSP); - vorbis_comment_clear(&_vorbisComment); - vorbis_info_clear(&_vorbisInfo); + AdvancedVideoDecoder::close(); - g_system->getMixer()->stopHandle(*_audHandle); + if (!_fileStream) + return; - _audStream = 0; - _vorbisPacket = false; - } - if (_theoraPacket) { + if (_videoTrack) { ogg_stream_clear(&_theoraOut); - th_decode_free(_theoraDecode); - th_comment_clear(&_theoraComment); - th_info_clear(&_theoraInfo); - _theoraDecode = 0; - _theoraPacket = false; + _videoTrack = 0; } - if (!_fileStream) - return; + if (_audioTrack) { + ogg_stream_clear(&_vorbisOut); + _audioTrack = 0; + } ogg_sync_clear(&_oggSync); + vorbis_info_clear(&_vorbisInfo); delete _fileStream; _fileStream = 0; - _surface.free(); - _displaySurface.pixels = 0; - _displaySurface.free(); - - reset(); + _hasVideo = _hasAudio = false; } -const Graphics::Surface *TheoraDecoder::decodeNextFrame() { +void TheoraDecoder::readNextPacket() { // 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 (_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); } - if (th_decode_packetin(_theoraDecode, &_oggPacket, NULL) == 0) { - _curFrame++; + // Update audio if we can + queueAudio(); + } + } - // Convert YUV data to RGB data - th_ycbcr_buffer yuv; - th_decode_ycbcr_out(_theoraDecode, yuv); - translateYUVtoRGBA(yuv); + // Then make sure we have enough audio buffered + ensureAudioBufferSize(); +} - if (_curFrame == 0) - _startTime = g_system->getMillis(); +TheoraDecoder::TheoraVideoTrack::TheoraVideoTrack(const Graphics::PixelFormat &format, th_info &theoraInfo, th_setup_info *theoraSetup) { + _theoraDecode = th_decode_alloc(&theoraInfo, theoraSetup); - double time = th_granule_time(_theoraDecode, _oggPacket.granulepos); + if (theoraInfo.pixel_fmt != TH_PF_420) + error("Only theora YUV420 is supported"); - // 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; + int postProcessingMax; + th_decode_ctl(_theoraDecode, TH_DECCTL_GET_PPLEVEL_MAX, &postProcessingMax, sizeof(postProcessingMax)); + th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &postProcessingMax, sizeof(postProcessingMax)); - // 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; - } + _surface.create(theoraInfo.frame_width, theoraInfo.frame_height, format); - // Queue more data - bufferData(); - while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) - queuePage(&_oggPage); - } + // 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 = format; + _displaySurface.pitch = _surface.pitch; - // Update audio if we can - queueAudio(); - } + // Set the frame rate + _frameRate = Common::Rational(theoraInfo.fps_numerator, theoraInfo.fps_denominator); - // 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); + _endOfVideo = false; + _nextFrameStartTime = 0.0; + _curFrame = -1; +} - bool queuedAudio = queueAudio(); - if ((_vorbisOut.e_o_s || _fileStream->eos()) && !queuedAudio) { - _endOfAudio = true; - break; - } - } +TheoraDecoder::TheoraVideoTrack::~TheoraVideoTrack() { + th_decode_free(_theoraDecode); - return &_displaySurface; + _surface.free(); + _displaySurface.pixels = 0; } -bool TheoraDecoder::queueAudio() { - if (!_audStream) - return false; - - // An audio buffer should have been allocated (either in the constructor or after queuing the current buffer) - if (!_audiobuf) { - warning("[TheoraDecoder::queueAudio] Invalid audio buffer"); - return false; +bool TheoraDecoder::TheoraVideoTrack::decodePacket(ogg_packet &oggPacket) { + if (th_decode_packetin(_theoraDecode, &oggPacket, 0) == 0) { + _curFrame++; + + // Convert YUV data to RGB data + th_ycbcr_buffer yuv; + th_decode_ycbcr_out(_theoraDecode, yuv); + translateYUVtoRGBA(yuv); + + 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; + + return true; } - bool queuedAudio = false; + return false; +} - for (;;) { - float **pcm; - - // if there's pending, decoded audio, grab it - int ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm); - if (ret > 0) { - int count = _audiobufFill / 2; - int maxsamples = ((AUDIOFD_FRAGSIZE - _audiobufFill) / _vorbisInfo.channels) >> 1; - int i; - for (i = 0; i < ret && i < maxsamples; i++) - for (int j = 0; j < _vorbisInfo.channels; j++) { - int val = CLIP((int)rint(pcm[j][i] * 32767.f), -32768, 32767); - _audiobuf[count++] = val; - } - - vorbis_synthesis_read(&_vorbisDSP, i); - _audiobufFill += (i * _vorbisInfo.channels) << 1; - - if (_audiobufFill == AUDIOFD_FRAGSIZE) { - byte flags = Audio::FLAG_16BITS | Audio::FLAG_STEREO; -#ifdef SCUMM_LITTLE_ENDIAN - flags |= Audio::FLAG_LITTLE_ENDIAN; -#endif - _audStream->queueBuffer((byte *)_audiobuf, AUDIOFD_FRAGSIZE, DisposeAfterUse::NO, flags); - - // The audio mixer is now responsible for the old audio buffer. - // We need to create a new one. - _audiobuf = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t)); - if (!_audiobuf) { - warning("[TheoraDecoder::queueAudio] Cannot allocate memory for audio buffer"); - return false; - } - - _audiobufFill = 0; - queuedAudio = true; - } - } else { - // no pending audio; is there a pending packet to decode? - if (ogg_stream_packetout(&_vorbisOut, &_oggPacket) > 0) { - if (vorbis_synthesis(&_vorbisBlock, &_oggPacket) == 0) // test for success! - vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock); - } else // we've buffered all we have, break out for now - return queuedAudio; - } - } +enum TheoraYUVBuffers { + kBufferY = 0, + kBufferU = 1, + kBufferV = 2 +}; - // Unreachable - return false; +void TheoraDecoder::TheoraVideoTrack::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) { + // Width and height of all buffers have to be divisible by 2. + assert((YUVBuffer[kBufferY].width & 1) == 0); + assert((YUVBuffer[kBufferY].height & 1) == 0); + assert((YUVBuffer[kBufferU].width & 1) == 0); + assert((YUVBuffer[kBufferV].width & 1) == 0); + + // UV images have to have a quarter of the Y image resolution + assert(YUVBuffer[kBufferU].width == YUVBuffer[kBufferY].width >> 1); + assert(YUVBuffer[kBufferV].width == YUVBuffer[kBufferY].width >> 1); + assert(YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height >> 1); + assert(YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height >> 1); + + Graphics::convertYUV420ToRGB(&_surface, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride); } -void TheoraDecoder::reset() { - VideoDecoder::reset(); +static vorbis_info *info = 0; - // FIXME: This does a rewind() instead of a reset()! +TheoraDecoder::VorbisAudioTrack::VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo) : _soundType(soundType) { + vorbis_synthesis_init(&_vorbisDSP, &vorbisInfo); + vorbis_block_init(&_vorbisDSP, &_vorbisBlock); + info = &vorbisInfo; - if (_fileStream) - _fileStream->seek(0); + _audStream = Audio::makeQueuingAudioStream(vorbisInfo.rate, vorbisInfo.channels); - _audiobufFill = 0; - _audiobufReady = false; + _audioBufferFill = 0; + _audioBuffer = 0; + _endOfAudio = false; +} - _curFrame = -1; +TheoraDecoder::VorbisAudioTrack::~VorbisAudioTrack() { + vorbis_dsp_clear(&_vorbisDSP); + vorbis_block_clear(&_vorbisBlock); + delete _audStream; + free(_audioBuffer); +} - _theoraPacket = 0; - _vorbisPacket = 0; +Audio::AudioStream *TheoraDecoder::VorbisAudioTrack::getAudioStream() const { + return _audStream; } -bool TheoraDecoder::endOfVideo() const { - return !isVideoLoaded() || (_endOfVideo && (!_audStream || (_audStream->endOfData() && _endOfAudio))); +#define AUDIOFD_FRAGSIZE 10240 + +static double rint(double v) { + return floor(v + 0.5); } -uint32 TheoraDecoder::getTimeToNextFrame() const { - if (endOfVideo() || _curFrame < 0) - return 0; +bool TheoraDecoder::VorbisAudioTrack::decodeSamples() { + float **pcm; - uint32 elapsedTime = getTime(); - uint32 nextFrameStartTime = (uint32)(_nextFrameStartTime * 1000); + // if there's pending, decoded audio, grab it + int ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm); - if (nextFrameStartTime <= elapsedTime) - return 0; + if (ret > 0) { + if (!_audioBuffer) { + _audioBuffer = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t)); + assert(_audioBuffer); + } - return nextFrameStartTime - elapsedTime; + int channels = _audStream->isStereo() ? 2 : 1; + int count = _audioBufferFill / 2; + int maxsamples = ((AUDIOFD_FRAGSIZE - _audioBufferFill) / channels) >> 1; + int i; + + for (i = 0; i < ret && i < maxsamples; i++) { + for (int j = 0; j < channels; j++) { + int val = CLIP((int)rint(pcm[j][i] * 32767.f), -32768, 32767); + _audioBuffer[count++] = val; + } + } + + vorbis_synthesis_read(&_vorbisDSP, i); + _audioBufferFill += (i * channels) << 1; + + if (_audioBufferFill == AUDIOFD_FRAGSIZE) { + byte flags = Audio::FLAG_16BITS; + + if (_audStream->isStereo()) + flags |= Audio::FLAG_STEREO; + +#ifdef SCUMM_LITTLE_ENDIAN + flags |= Audio::FLAG_LITTLE_ENDIAN; +#endif + + _audStream->queueBuffer((byte *)_audioBuffer, AUDIOFD_FRAGSIZE, DisposeAfterUse::YES, flags); + + // The audio mixer is now responsible for the old audio buffer. + // We need to create a new one. + _audioBuffer = 0; + _audioBufferFill = 0; + } + + return true; + } + + return false; } -uint32 TheoraDecoder::getTime() const { - if (_audStream) - return g_system->getMixer()->getSoundElapsedTime(*_audHandle); +bool TheoraDecoder::VorbisAudioTrack::hasAudio() const { + return _audStream->numQueuedStreams() > 0; +} - return VideoDecoder::getTime(); +bool TheoraDecoder::VorbisAudioTrack::needsAudio() const { + // TODO: 5 is very arbitrary. We probably should do something like QuickTime does. + return !_endOfAudio && _audStream->numQueuedStreams() < 5; } -void TheoraDecoder::pauseVideoIntern(bool pause) { - if (_audStream) - g_system->getMixer()->pauseHandle(*_audHandle, pause); +void TheoraDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &oggPacket) { + if (vorbis_synthesis(&_vorbisBlock, &oggPacket) == 0) // test for success + vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock); } -enum TheoraYUVBuffers { - kBufferY = 0, - kBufferU = 1, - kBufferV = 2 -}; +void TheoraDecoder::queuePage(ogg_page *page) { + if (_hasVideo) + ogg_stream_pagein(&_theoraOut, page); -void TheoraDecoder::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) { - // Width and height of all buffers have to be divisible by 2. - assert((YUVBuffer[kBufferY].width & 1) == 0); - assert((YUVBuffer[kBufferY].height & 1) == 0); - assert((YUVBuffer[kBufferU].width & 1) == 0); - assert((YUVBuffer[kBufferV].width & 1) == 0); + if (_hasAudio) + ogg_stream_pagein(&_vorbisOut, page); +} - // UV images have to have a quarter of the Y image resolution - assert(YUVBuffer[kBufferU].width == YUVBuffer[kBufferY].width >> 1); - assert(YUVBuffer[kBufferV].width == YUVBuffer[kBufferY].width >> 1); - assert(YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height >> 1); - assert(YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height >> 1); +int TheoraDecoder::bufferData() { + char *buffer = ogg_sync_buffer(&_oggSync, 4096); + int bytes = _fileStream->read(buffer, 4096); - Graphics::convertYUV420ToRGB(&_surface, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride); + ogg_sync_wrote(&_oggSync, bytes); + + return bytes; } -void TheoraDecoder::updateVolume() { - if (g_system->getMixer()->isSoundHandleActive(*_audHandle)) - g_system->getMixer()->setChannelVolume(*_audHandle, getVolume()); +bool TheoraDecoder::queueAudio() { + if (!_hasAudio) + return false; + + bool queuedAudio = false; + + for (;;) { + if (_audioTrack->decodeSamples()) { + // we queued some pending audio + queuedAudio = true; + } else if (ogg_stream_packetout(&_vorbisOut, &_oggPacket) > 0) { + // no pending audio; is there a pending packet to decode? + _audioTrack->synthesizePacket(_oggPacket); + } else { + // we've buffered all we have, break out for now + break; + } + } + + return queuedAudio; } -void TheoraDecoder::updateBalance() { - if (g_system->getMixer()->isSoundHandleActive(*_audHandle)) - g_system->getMixer()->setChannelBalance(*_audHandle, getBalance()); +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; + } + } } } // End of namespace Video diff --git a/video/theora_decoder.h b/video/theora_decoder.h index 459fc064d3..2244f7550d 100644 --- a/video/theora_decoder.h +++ b/video/theora_decoder.h @@ -29,9 +29,7 @@ #include "common/rational.h" #include "video/video_decoder.h" -#include "audio/audiostream.h" #include "audio/mixer.h" -#include "graphics/pixelformat.h" #include "graphics/surface.h" #include <theora/theoradec.h> @@ -41,6 +39,11 @@ namespace Common { class SeekableReadStream; } +namespace Audio { +class AudioStream; +class QueuingAudioStream; +} + namespace Video { /** @@ -49,7 +52,7 @@ namespace Video { * Video decoder used in engines: * - sword25 */ -class TheoraDecoder : public VideoDecoder { +class TheoraDecoder : public AdvancedVideoDecoder { public: TheoraDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType); virtual ~TheoraDecoder(); @@ -60,81 +63,91 @@ public: */ bool loadStream(Common::SeekableReadStream *stream); void close(); - void reset(); - /** - * Decode the next frame and return the frame's surface - * @note the return surface should *not* be freed - * @note this may return 0, in which case the last frame should be kept on screen - */ - const Graphics::Surface *decodeNextFrame(); +protected: + void readNextPacket(); - bool isVideoLoaded() const { return _fileStream != 0; } - uint16 getWidth() const { return _displaySurface.w; } - uint16 getHeight() const { return _displaySurface.h; } +private: + class TheoraVideoTrack : public VideoTrack { + public: + TheoraVideoTrack(const Graphics::PixelFormat &format, th_info &theoraInfo, th_setup_info *theoraSetup); + ~TheoraVideoTrack(); - uint32 getFrameCount() const { - // It is not possible to get frame count easily - // I.e. seeking is required - assert(0); - return 0; - } + bool endOfTrack() const { return _endOfVideo; } + uint16 getWidth() const { return _displaySurface.w; } + uint16 getHeight() const { return _displaySurface.h; } + Graphics::PixelFormat getPixelFormat() const { return _displaySurface.format; } + int getCurFrame() const { return _curFrame; } + uint32 getNextFrameStartTime() const { return (uint32)(_nextFrameStartTime * 1000); } + const Graphics::Surface *decodeNextFrame() { return &_displaySurface; } - Graphics::PixelFormat getPixelFormat() const { return _displaySurface.format; } - uint32 getTime() const; - uint32 getTimeToNextFrame() const; + bool decodePacket(ogg_packet &oggPacket); + void setEndOfVideo() { _endOfVideo = true; } - bool endOfVideo() const; + private: + int _curFrame; + bool _endOfVideo; + Common::Rational _frameRate; + double _nextFrameStartTime; -protected: - // VideoDecoder API - void updateVolume(); - void updateBalance(); - void pauseVideoIntern(bool pause); + Graphics::Surface _surface; + Graphics::Surface _displaySurface; + + th_dec_ctx *_theoraDecode; + + void translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer); + }; + + class VorbisAudioTrack : public AudioTrack { + public: + VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo); + ~VorbisAudioTrack(); + + Audio::Mixer::SoundType getSoundType() const { return _soundType; } + + bool decodeSamples(); + bool hasAudio() const; + bool needsAudio() const; + void synthesizePacket(ogg_packet &oggPacket); + void setEndOfAudio() { _endOfAudio = true; } + + protected: + Audio::AudioStream *getAudioStream() const; + + private: + // single audio fragment audio buffering + int _audioBufferFill; + ogg_int16_t *_audioBuffer; + + Audio::Mixer::SoundType _soundType; + Audio::QueuingAudioStream *_audStream; + + vorbis_block _vorbisBlock; + vorbis_dsp_state _vorbisDSP; + + bool _endOfAudio; + }; -private: void queuePage(ogg_page *page); - bool queueAudio(); int bufferData(); - void translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer); + bool queueAudio(); + void ensureAudioBufferSize(); Common::SeekableReadStream *_fileStream; - Graphics::Surface _surface; - Graphics::Surface _displaySurface; - Common::Rational _frameRate; - double _nextFrameStartTime; - bool _endOfVideo; - bool _endOfAudio; Audio::Mixer::SoundType _soundType; - Audio::SoundHandle *_audHandle; - Audio::QueuingAudioStream *_audStream; ogg_sync_state _oggSync; ogg_page _oggPage; ogg_packet _oggPacket; - ogg_stream_state _vorbisOut; - ogg_stream_state _theoraOut; - th_info _theoraInfo; - th_comment _theoraComment; - th_dec_ctx *_theoraDecode; - th_setup_info *_theoraSetup; - vorbis_info _vorbisInfo; - vorbis_dsp_state _vorbisDSP; - vorbis_block _vorbisBlock; - vorbis_comment _vorbisComment; - int _theoraPacket; - int _vorbisPacket; + ogg_stream_state _theoraOut, _vorbisOut; + bool _hasVideo, _hasAudio; - int _ppLevelMax; - int _ppLevel; - int _ppInc; + vorbis_info _vorbisInfo; - // single audio fragment audio buffering - int _audiobufFill; - bool _audiobufReady; - ogg_int16_t *_audiobuf; + TheoraVideoTrack *_videoTrack; + VorbisAudioTrack *_audioTrack; }; } // End of namespace Video |