mirror of
https://github.com/libretro/scummvm.git
synced 2026-11-07 12:38:04 -10:00
288 lines
8.0 KiB
C++
288 lines
8.0 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "video/asf_decoder.h"
|
|
#include "video/asf.h"
|
|
#include "common/bufferedstream.h"
|
|
#include "common/memstream.h"
|
|
#include "common/textconsole.h"
|
|
#include "image/codecs/codec.h"
|
|
#ifdef USE_VC1
|
|
#include "image/codecs/vc1.h"
|
|
#endif
|
|
#include "graphics/surface.h"
|
|
|
|
namespace Video {
|
|
|
|
class ASFDecoder::ASFVideoTrack : public VideoTrack {
|
|
public:
|
|
ASFVideoTrack() : _codec(nullptr), _pending(nullptr), _width(0), _height(0),
|
|
_frame(-1), _time(0), _vc1(false) {}
|
|
~ASFVideoTrack() override { delete _codec; _surface.free(); }
|
|
bool open(const ASFDemuxer::Stream &stream) {
|
|
if (stream.typeData.size() < 51)
|
|
return false;
|
|
Common::MemoryReadStream format(stream.typeData.data(), stream.typeData.size());
|
|
format.skip(9);
|
|
format.readUint16LE();
|
|
uint32 headerSize = format.readUint32LE();
|
|
int32 width = format.readSint32LE();
|
|
int32 height = format.readSint32LE();
|
|
format.readUint16LE();
|
|
uint16 bits = format.readUint16LE();
|
|
uint32 tag = format.readUint32BE();
|
|
if (headerSize < 40 || headerSize > stream.typeData.size() - 11 ||
|
|
width <= 0 || width > 65535 || height <= 0 || height > 65535)
|
|
return false;
|
|
_width = width;
|
|
_height = height;
|
|
_vc1 = tag == MKTAG('W','M','V','3') || tag == MKTAG('w','m','v','3') ||
|
|
tag == MKTAG('W','V','C','1') || tag == MKTAG('w','v','c','1') || tag == MKTAG('W','M','V','A');
|
|
_codec = Image::createBitmapCodec(tag, 0, width, height, bits);
|
|
if (!_codec)
|
|
return false;
|
|
#ifdef USE_VC1
|
|
if (_vc1) {
|
|
Common::MemoryReadStream extra(stream.typeData.data() + 51, headerSize - 40);
|
|
if (!static_cast<Image::WMV3Decoder *>(_codec)->setExtraData(extra))
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
uint16 getWidth() const override { return _width; }
|
|
uint16 getHeight() const override { return _height; }
|
|
Graphics::PixelFormat getPixelFormat() const override { return _codec->getPixelFormat(); }
|
|
bool setOutputPixelFormat(const Graphics::PixelFormat &format) override {
|
|
return format == getPixelFormat();
|
|
}
|
|
int getCurFrame() const override { return _frame; }
|
|
uint32 getNextFrameStartTime() const override { return _time; }
|
|
bool endOfTrack() const override { return !_pending; }
|
|
const Graphics::Surface *decodeNextFrame() override { return _surface.getPixels() ? &_surface : nullptr; }
|
|
void consume() {
|
|
if (_pending) {
|
|
_surface.copyFrom(*_pending);
|
|
_pending = nullptr;
|
|
++_frame;
|
|
}
|
|
}
|
|
bool decode(const ASFDemuxer::Object &object) {
|
|
_times.push_back(object.pts != ASFDemuxer::kNoTimestamp ? object.pts : object.dts);
|
|
Common::MemoryReadStream data(object.data.data(), object.data.size());
|
|
_pending = _codec->decodeFrame(data);
|
|
return setTime();
|
|
}
|
|
bool drain() {
|
|
#ifdef USE_VC1
|
|
if (_vc1)
|
|
_pending = static_cast<Image::WMV3Decoder *>(_codec)->drainFrame();
|
|
#endif
|
|
return setTime();
|
|
}
|
|
bool failed() const {
|
|
#ifdef USE_VC1
|
|
if (_vc1)
|
|
return static_cast<Image::WMV3Decoder *>(_codec)->hasError();
|
|
#endif
|
|
return !_vc1 && !_pending;
|
|
}
|
|
private:
|
|
bool setTime() {
|
|
if (!_pending)
|
|
return false;
|
|
if (_times.empty())
|
|
return false;
|
|
uint first = 0;
|
|
for (uint i = 1; i < _times.size(); ++i)
|
|
if (_times[i] < _times[first])
|
|
first = i;
|
|
_time = MAX<int64>(0, _times[first]);
|
|
_times.remove_at(first);
|
|
return true;
|
|
}
|
|
Image::Codec *_codec;
|
|
const Graphics::Surface *_pending;
|
|
Graphics::Surface _surface;
|
|
Common::Array<int64> _times;
|
|
uint16 _width, _height;
|
|
int _frame;
|
|
uint32 _time;
|
|
bool _vc1;
|
|
};
|
|
|
|
class ASFDecoder::ASFAudioTrack : public AudioTrack {
|
|
public:
|
|
ASFAudioTrack(Audio::Mixer::SoundType soundType) : AudioTrack(soundType), _queue(nullptr) {}
|
|
~ASFAudioTrack() override { delete _queue; }
|
|
bool open(const ASFDemuxer::Stream &stream) {
|
|
if (!_codec.open(stream))
|
|
return false;
|
|
_queue = Audio::makeQueuingAudioStream(_codec.getRate(), _codec.isStereo());
|
|
return true;
|
|
}
|
|
void decode(const ASFDemuxer::Object &object) {
|
|
Audio::AudioStream *audio = _codec.decode(object);
|
|
if (audio)
|
|
_queue->queueAudioStream(audio, DisposeAfterUse::YES);
|
|
}
|
|
void finish() { _queue->finish(); }
|
|
bool hasEnoughPackets() const { return _queue->numQueuedStreams() > 25; }
|
|
protected:
|
|
Audio::AudioStream *getAudioStream() const override { return _queue; }
|
|
private:
|
|
ASFAudioCodec _codec;
|
|
Audio::QueuingAudioStream *_queue;
|
|
};
|
|
|
|
ASFDecoder::ASFDecoder() : _input(nullptr), _videoQueueBytes(0), _video(nullptr), _audio(nullptr),
|
|
_videoID(0), _audioID(0), _ended(false) {}
|
|
|
|
ASFDecoder::~ASFDecoder() {
|
|
close();
|
|
}
|
|
|
|
void ASFDecoder::close() {
|
|
VideoDecoder::close();
|
|
_video = nullptr;
|
|
_audio = nullptr;
|
|
_demux.close();
|
|
_videoObjects.clear();
|
|
_videoQueueBytes = 0;
|
|
delete _input;
|
|
_input = nullptr;
|
|
_videoID = _audioID = 0;
|
|
_ended = false;
|
|
}
|
|
|
|
bool ASFDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
_input = Common::wrapBufferedSeekableReadStream(stream, 1024 * 1024, DisposeAfterUse::YES);
|
|
if (!_demux.open(_input)) {
|
|
close();
|
|
return false;
|
|
}
|
|
for (const auto &info : _demux.getStreams()) {
|
|
if (info.type == ASFDemuxer::kVideo && !_video) {
|
|
ASFVideoTrack *track = new ASFVideoTrack();
|
|
if (!track->open(info)) {
|
|
delete track;
|
|
delete _audio;
|
|
_audio = nullptr;
|
|
close();
|
|
return false;
|
|
}
|
|
_video = track;
|
|
_videoID = info.id;
|
|
} else if (info.type == ASFDemuxer::kAudio && !_audio) {
|
|
ASFAudioTrack *track = new ASFAudioTrack(getSoundType());
|
|
if (!track->open(info)) {
|
|
delete track;
|
|
delete _video;
|
|
_video = nullptr;
|
|
close();
|
|
return false;
|
|
}
|
|
_audio = track;
|
|
_audioID = info.id;
|
|
}
|
|
}
|
|
if (!_video || !prepareFrame()) {
|
|
delete _video;
|
|
delete _audio;
|
|
_video = nullptr;
|
|
_audio = nullptr;
|
|
close();
|
|
return false;
|
|
}
|
|
bufferPackets();
|
|
addTrack(_video);
|
|
if (_audio)
|
|
addTrack(_audio);
|
|
return true;
|
|
}
|
|
|
|
bool ASFDecoder::readObject(ASFDemuxer::Object &object) {
|
|
ASFDemuxer::ReadResult result = _demux.readObject(object);
|
|
if (result == ASFDemuxer::kObject)
|
|
return true;
|
|
_ended = true;
|
|
if (_audio)
|
|
_audio->finish();
|
|
if (result == ASFDemuxer::kError)
|
|
warning("ASF: Invalid packet");
|
|
return false;
|
|
}
|
|
|
|
void ASFDecoder::bufferPackets() {
|
|
while (!_ended) {
|
|
if (_videoQueueBytes > 15 * 1024 * 1024 ||
|
|
(_videoObjects.size() > 25 && (!_audio || _audio->hasEnoughPackets())))
|
|
break;
|
|
ASFDemuxer::Object object;
|
|
if (!readObject(object))
|
|
break;
|
|
if (_audio && object.stream == _audioID) {
|
|
_audio->decode(object);
|
|
} else if (object.stream == _videoID) {
|
|
_videoQueueBytes += object.data.size();
|
|
_videoObjects.push(object);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ASFDecoder::prepareFrame() {
|
|
while (!_videoObjects.empty() || !_ended) {
|
|
ASFDemuxer::Object object;
|
|
if (!_videoObjects.empty()) {
|
|
object = _videoObjects.pop();
|
|
_videoQueueBytes -= object.data.size();
|
|
} else if (!readObject(object))
|
|
break;
|
|
if (_audio && object.stream == _audioID) {
|
|
_audio->decode(object);
|
|
} else if (_video && object.stream == _videoID) {
|
|
if (_video->decode(object))
|
|
return true;
|
|
if (_video->failed()) {
|
|
warning("ASF: Video decoding failed");
|
|
_ended = true;
|
|
_videoObjects.clear();
|
|
_videoQueueBytes = 0;
|
|
if (_audio)
|
|
_audio->finish();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return _video && _video->drain();
|
|
}
|
|
|
|
void ASFDecoder::readNextPacket() {
|
|
if (_video && !_video->endOfTrack()) {
|
|
_video->consume();
|
|
prepareFrame();
|
|
bufferPackets();
|
|
}
|
|
}
|
|
|
|
} // End of namespace Video
|