2010-08-04 14:53:07 +00:00
|
|
|
/* ScummVM - Graphic Adventure Engine
|
|
|
|
*
|
|
|
|
* ScummVM is the legal property of its developers, whose names
|
|
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
|
|
* file distributed with this source distribution.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2011-01-30 20:39:02 +00:00
|
|
|
* Source is based on the player example from libvorbis package,
|
|
|
|
* available at: http://svn.xiph.org/trunk/theora/examples/player_example.c
|
2010-08-04 14:53:07 +00:00
|
|
|
*
|
|
|
|
* THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE.
|
|
|
|
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS
|
|
|
|
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE
|
|
|
|
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.
|
|
|
|
*
|
|
|
|
* THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009
|
|
|
|
* by the Xiph.Org Foundation and contributors http://www.xiph.org/
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "sword25/fmv/theora_decoder.h"
|
2010-10-13 13:07:16 +00:00
|
|
|
|
2010-10-13 13:32:30 +00:00
|
|
|
#ifdef USE_THEORADEC
|
2010-08-04 14:53:07 +00:00
|
|
|
#include "common/system.h"
|
2011-04-24 11:34:27 +03:00
|
|
|
#include "common/textconsole.h"
|
2011-05-17 18:05:40 -04:00
|
|
|
#include "common/util.h"
|
|
|
|
#include "graphics/yuv_to_rgb.h"
|
2011-02-09 01:09:01 +00:00
|
|
|
#include "audio/decoders/raw.h"
|
2011-01-23 16:14:51 +00:00
|
|
|
#include "sword25/kernel/common.h"
|
2010-08-04 14:53:07 +00:00
|
|
|
|
|
|
|
namespace Sword25 {
|
|
|
|
|
2010-08-05 12:16:21 +00:00
|
|
|
#define AUDIOFD_FRAGSIZE 10240
|
|
|
|
|
2010-08-17 09:52:50 +00:00
|
|
|
static double rint(double v) {
|
|
|
|
return floor(v + 0.5);
|
|
|
|
}
|
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
TheoraDecoder::TheoraDecoder(Audio::Mixer::SoundType soundType) {
|
2010-08-04 14:53:07 +00:00
|
|
|
_fileStream = 0;
|
|
|
|
|
2010-09-01 07:35:38 +00:00
|
|
|
_theoraPacket = 0;
|
|
|
|
_vorbisPacket = 0;
|
2010-09-01 08:42:51 +00:00
|
|
|
_theoraDecode = 0;
|
2010-09-01 07:35:38 +00:00
|
|
|
_theoraSetup = 0;
|
2011-05-16 22:47:41 -04:00
|
|
|
_nextFrameStartTime = 0.0;
|
2010-09-01 07:35:38 +00:00
|
|
|
|
2010-08-04 21:13:43 +00:00
|
|
|
_soundType = soundType;
|
|
|
|
_audStream = 0;
|
|
|
|
_audHandle = new Audio::SoundHandle();
|
2010-08-31 18:37:59 +00:00
|
|
|
|
2010-09-01 07:35:38 +00:00
|
|
|
ogg_sync_init(&_oggSync);
|
2010-08-31 18:37:59 +00:00
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
_curFrame = -1;
|
2011-01-30 20:39:02 +00:00
|
|
|
_audiobuf = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t));
|
2010-08-31 18:37:59 +00:00
|
|
|
|
|
|
|
reset();
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TheoraDecoder::~TheoraDecoder() {
|
|
|
|
close();
|
2010-09-01 07:35:38 +00:00
|
|
|
delete _fileStream;
|
2010-08-04 21:13:43 +00:00
|
|
|
delete _audHandle;
|
2010-08-31 18:37:59 +00:00
|
|
|
free(_audiobuf);
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TheoraDecoder::queuePage(ogg_page *page) {
|
2010-08-06 13:13:25 +00:00
|
|
|
if (_theoraPacket)
|
|
|
|
ogg_stream_pagein(&_theoraOut, page);
|
2010-08-04 14:53:07 +00:00
|
|
|
|
2010-08-06 13:13:25 +00:00
|
|
|
if (_vorbisPacket)
|
|
|
|
ogg_stream_pagein(&_vorbisOut, page);
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int TheoraDecoder::bufferData() {
|
|
|
|
char *buffer = ogg_sync_buffer(&_oggSync, 4096);
|
|
|
|
int bytes = _fileStream->read(buffer, 4096);
|
|
|
|
|
|
|
|
ogg_sync_wrote(&_oggSync, bytes);
|
|
|
|
|
2010-08-31 18:37:59 +00:00
|
|
|
return bytes;
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
2011-02-07 17:54:16 +00:00
|
|
|
bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) {
|
2010-08-04 14:53:07 +00:00
|
|
|
close();
|
|
|
|
|
2011-05-16 22:47:41 -04:00
|
|
|
_endOfAudio = false;
|
|
|
|
_endOfVideo = false;
|
2010-08-31 09:45:21 +00:00
|
|
|
_fileStream = stream;
|
2010-08-04 14:53:07 +00:00
|
|
|
|
|
|
|
// 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
|
2011-05-14 22:48:44 -04:00
|
|
|
bool foundHeader = false;
|
|
|
|
while (!foundHeader) {
|
2010-08-04 14:53:07 +00:00
|
|
|
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);
|
2011-05-14 22:48:44 -04:00
|
|
|
foundHeader = true;
|
2010-08-04 14:53:07 +00:00
|
|
|
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);
|
2010-08-05 12:16:21 +00:00
|
|
|
|
2010-08-04 14:53:07 +00:00
|
|
|
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).",
|
2010-08-06 13:13:25 +00:00
|
|
|
_theoraInfo.frame_width, _theoraInfo.frame_height, _theoraInfo.pic_x, _theoraInfo.pic_y);
|
2010-08-04 14:53:07 +00:00
|
|
|
|
2010-08-31 18:37:59 +00:00
|
|
|
switch (_theoraInfo.colorspace){
|
|
|
|
case TH_CS_UNSPECIFIED:
|
|
|
|
/* nothing to report */
|
2010-12-31 15:19:42 +00:00
|
|
|
break;
|
2010-08-31 18:37:59 +00:00
|
|
|
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);
|
2010-11-03 00:19:28 +00:00
|
|
|
if (value) {
|
|
|
|
memcpy(value, _theoraComment.user_comments[i], len);
|
|
|
|
value[len] = '\0';
|
|
|
|
debug(1, "\t%s", value);
|
|
|
|
free(value);
|
|
|
|
}
|
2010-08-31 18:37:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-08-04 14:53:07 +00:00
|
|
|
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);
|
2010-08-31 18:37:59 +00:00
|
|
|
_theoraSetup = 0;
|
2010-08-04 14:53:07 +00:00
|
|
|
|
|
|
|
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.",
|
2010-08-06 13:13:25 +00:00
|
|
|
_vorbisOut.serialno, _vorbisInfo.channels, _vorbisInfo.rate);
|
2011-05-16 22:47:41 -04:00
|
|
|
|
|
|
|
_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);
|
2010-08-04 14:53:07 +00:00
|
|
|
} else {
|
|
|
|
// tear down the partial vorbis setup
|
|
|
|
vorbis_info_clear(&_vorbisInfo);
|
|
|
|
vorbis_comment_clear(&_vorbisComment);
|
2011-05-16 22:47:41 -04:00
|
|
|
_endOfAudio = true;
|
2010-08-04 21:13:43 +00:00
|
|
|
}
|
2010-08-04 14:53:07 +00:00
|
|
|
|
2011-05-23 10:03:59 -04:00
|
|
|
_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;
|
2010-08-05 12:16:21 +00:00
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
// Set the frame rate
|
|
|
|
_frameRate = Common::Rational(_theoraInfo.fps_numerator, _theoraInfo.fps_denominator);
|
|
|
|
|
2010-08-04 14:53:07 +00:00
|
|
|
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);
|
2010-08-04 21:13:43 +00:00
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
g_system->getMixer()->stopHandle(*_audHandle);
|
|
|
|
|
2010-08-04 21:13:43 +00:00
|
|
|
_audStream = 0;
|
2010-09-01 07:35:38 +00:00
|
|
|
_vorbisPacket = false;
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
if (_theoraPacket) {
|
|
|
|
ogg_stream_clear(&_theoraOut);
|
|
|
|
th_decode_free(_theoraDecode);
|
|
|
|
th_comment_clear(&_theoraComment);
|
|
|
|
th_info_clear(&_theoraInfo);
|
2010-08-31 18:37:59 +00:00
|
|
|
_theoraDecode = 0;
|
2010-09-01 07:35:38 +00:00
|
|
|
_theoraPacket = false;
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!_fileStream)
|
|
|
|
return;
|
|
|
|
|
2010-08-31 09:45:21 +00:00
|
|
|
ogg_sync_clear(&_oggSync);
|
|
|
|
|
2010-08-04 14:53:07 +00:00
|
|
|
delete _fileStream;
|
|
|
|
_fileStream = 0;
|
|
|
|
|
2011-05-23 10:03:59 -04:00
|
|
|
_surface.free();
|
|
|
|
_displaySurface.pixels = 0;
|
|
|
|
_displaySurface.free();
|
2010-08-04 14:53:07 +00:00
|
|
|
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
2010-12-16 01:35:13 +00:00
|
|
|
const Graphics::Surface *TheoraDecoder::decodeNextFrame() {
|
2011-05-16 22:47:41 -04:00
|
|
|
// First, let's get our frame
|
|
|
|
while (_theoraPacket) {
|
2010-08-04 14:53:07 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2011-01-30 20:39:02 +00:00
|
|
|
|
|
|
|
if (th_decode_packetin(_theoraDecode, &_oggPacket, NULL) == 0) {
|
2010-08-04 14:53:07 +00:00
|
|
|
_curFrame++;
|
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
// Convert YUV data to RGB data
|
|
|
|
th_ycbcr_buffer yuv;
|
|
|
|
th_decode_ycbcr_out(_theoraDecode, yuv);
|
|
|
|
translateYUVtoRGBA(yuv);
|
2010-08-04 14:53:07 +00:00
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
if (_curFrame == 0)
|
|
|
|
_startTime = g_system->getMillis();
|
|
|
|
|
2011-05-16 22:47:41 -04:00
|
|
|
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;
|
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
// break out
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
2011-05-16 22:47:41 -04:00
|
|
|
// If we can't get any more frames, we're done.
|
2011-05-17 23:56:15 -04:00
|
|
|
if (_theoraOut.e_o_s || _fileStream->eos()) {
|
2011-05-16 22:47:41 -04:00
|
|
|
_endOfVideo = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
// Queue more data
|
|
|
|
bufferData();
|
|
|
|
while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
|
|
|
|
queuePage(&_oggPage);
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
2011-05-16 22:47:41 -04:00
|
|
|
|
|
|
|
// Update audio if we can
|
|
|
|
queueAudio();
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
2011-05-16 22:47:41 -04:00
|
|
|
// 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);
|
2010-08-04 14:53:07 +00:00
|
|
|
|
2011-05-16 22:47:41 -04:00
|
|
|
bool queuedAudio = queueAudio();
|
2011-05-17 23:56:15 -04:00
|
|
|
if ((_vorbisOut.e_o_s || _fileStream->eos()) && !queuedAudio) {
|
2011-05-16 22:47:41 -04:00
|
|
|
_endOfAudio = true;
|
|
|
|
break;
|
|
|
|
}
|
2010-10-30 08:21:10 +00:00
|
|
|
}
|
|
|
|
|
2011-05-23 10:03:59 -04:00
|
|
|
return &_displaySurface;
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
2011-05-16 22:47:41 -04:00
|
|
|
bool TheoraDecoder::queueAudio() {
|
|
|
|
if (!_audStream)
|
|
|
|
return false;
|
|
|
|
|
2011-06-04 06:05:01 +08:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2011-05-16 22:47:41 -04:00
|
|
|
bool queuedAudio = 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) {
|
2011-05-23 16:48:39 +02:00
|
|
|
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);
|
2011-05-16 22:47:41 -04:00
|
|
|
|
|
|
|
// 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));
|
2011-06-04 06:05:01 +08:00
|
|
|
if (!_audiobuf) {
|
|
|
|
warning("[TheoraDecoder::queueAudio] Cannot allocate memory for audio buffer");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-05-16 22:47:41 -04:00
|
|
|
_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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unreachable
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-08-04 14:53:07 +00:00
|
|
|
void TheoraDecoder::reset() {
|
2011-05-16 22:47:41 -04:00
|
|
|
VideoDecoder::reset();
|
2010-08-31 18:37:59 +00:00
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
// FIXME: This does a rewind() instead of a reset()!
|
|
|
|
|
2010-08-04 14:53:07 +00:00
|
|
|
if (_fileStream)
|
|
|
|
_fileStream->seek(0);
|
|
|
|
|
|
|
|
_audiobufFill = 0;
|
2010-08-04 21:13:43 +00:00
|
|
|
_audiobufReady = false;
|
2010-08-31 18:37:59 +00:00
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
_curFrame = -1;
|
2010-08-31 21:07:36 +00:00
|
|
|
|
|
|
|
_theoraPacket = 0;
|
|
|
|
_vorbisPacket = 0;
|
2010-08-04 14:53:07 +00:00
|
|
|
}
|
|
|
|
|
2010-08-31 18:37:59 +00:00
|
|
|
bool TheoraDecoder::endOfVideo() const {
|
2011-05-16 22:47:41 -04:00
|
|
|
return !isVideoLoaded() || (_endOfVideo && (!_audStream || (_audStream->endOfData() && _endOfAudio)));
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 TheoraDecoder::getTimeToNextFrame() const {
|
|
|
|
if (endOfVideo() || _curFrame < 0)
|
|
|
|
return 0;
|
|
|
|
|
2012-05-12 21:28:13 -04:00
|
|
|
uint32 elapsedTime = getTime();
|
2011-05-16 22:47:41 -04:00
|
|
|
uint32 nextFrameStartTime = (uint32)(_nextFrameStartTime * 1000);
|
|
|
|
|
|
|
|
if (nextFrameStartTime <= elapsedTime)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return nextFrameStartTime - elapsedTime;
|
2010-08-31 18:37:59 +00:00
|
|
|
}
|
|
|
|
|
2012-05-12 21:28:13 -04:00
|
|
|
uint32 TheoraDecoder::getTime() const {
|
2011-05-14 22:48:44 -04:00
|
|
|
if (_audStream)
|
|
|
|
return g_system->getMixer()->getSoundElapsedTime(*_audHandle);
|
2010-08-04 21:13:43 +00:00
|
|
|
|
2012-05-12 21:28:13 -04:00
|
|
|
return VideoDecoder::getTime();
|
2010-08-04 21:13:43 +00:00
|
|
|
}
|
|
|
|
|
2011-05-18 10:00:35 -04:00
|
|
|
void TheoraDecoder::pauseVideoIntern(bool pause) {
|
|
|
|
if (_audStream)
|
|
|
|
g_system->getMixer()->pauseHandle(*_audHandle, pause);
|
|
|
|
}
|
|
|
|
|
2011-01-30 20:39:02 +00:00
|
|
|
enum TheoraYUVBuffers {
|
|
|
|
kBufferY = 0,
|
|
|
|
kBufferU = 1,
|
|
|
|
kBufferV = 2
|
|
|
|
};
|
2011-01-23 16:14:51 +00:00
|
|
|
|
2011-05-14 22:48:44 -04:00
|
|
|
void TheoraDecoder::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) {
|
2011-01-23 16:14:51 +00:00
|
|
|
// Width and height of all buffers have to be divisible by 2.
|
2011-01-30 20:39:02 +00:00
|
|
|
assert((YUVBuffer[kBufferY].width & 1) == 0);
|
|
|
|
assert((YUVBuffer[kBufferY].height & 1) == 0);
|
|
|
|
assert((YUVBuffer[kBufferU].width & 1) == 0);
|
|
|
|
assert((YUVBuffer[kBufferV].width & 1) == 0);
|
2011-01-23 16:14:51 +00:00
|
|
|
|
|
|
|
// UV images have to have a quarter of the Y image resolution
|
2011-01-30 20:39:02 +00:00
|
|
|
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);
|
|
|
|
|
2011-05-23 10:03:59 -04:00
|
|
|
Graphics::convertYUV420ToRGB(&_surface, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride);
|
2011-01-23 16:14:51 +00:00
|
|
|
}
|
|
|
|
|
2010-08-04 14:53:07 +00:00
|
|
|
} // End of namespace Sword25
|
2010-10-13 13:07:16 +00:00
|
|
|
|
|
|
|
#endif
|