mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-13 21:31:53 +00:00
863 lines
21 KiB
C++
863 lines
21 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 "audio/audiostream.h"
|
|
#include "audio/decoders/raw.h"
|
|
#include "audio/decoders/ac3.h"
|
|
#include "audio/decoders/mp3.h"
|
|
#include "common/debug.h"
|
|
#include "common/endian.h"
|
|
#include "common/stream.h"
|
|
#include "common/memstream.h"
|
|
#include "common/system.h"
|
|
#include "common/textconsole.h"
|
|
|
|
#include "video/mpegps_decoder.h"
|
|
#include "image/codecs/mpeg.h"
|
|
|
|
// The demuxing code is based on libav's demuxing code
|
|
|
|
namespace Video {
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Decoder - This is the part that takes a packet and figures out what to do
|
|
// with it.
|
|
// --------------------------------------------------------------------------
|
|
|
|
enum {
|
|
kStartCodePack = 0x1BA,
|
|
kStartCodeSystemHeader = 0x1BB,
|
|
kStartCodeProgramStreamMap = 0x1BC,
|
|
kStartCodePrivateStream1 = 0x1BD,
|
|
kStartCodePaddingStream = 0x1BE,
|
|
kStartCodePrivateStream2 = 0x1BF
|
|
};
|
|
|
|
MPEGPSDecoder::MPEGPSDecoder(double decibel) {
|
|
_decibel = decibel;
|
|
_demuxer = new MPEGPSDemuxer();
|
|
}
|
|
|
|
MPEGPSDecoder::~MPEGPSDecoder() {
|
|
close();
|
|
delete _demuxer;
|
|
}
|
|
|
|
bool MPEGPSDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
if (!_demuxer->loadStream(stream)) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (!addFirstVideoTrack()) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MPEGPSDecoder::close() {
|
|
VideoDecoder::close();
|
|
_demuxer->close();
|
|
_streamMap.clear();
|
|
}
|
|
|
|
MPEGPSDecoder::MPEGStream *MPEGPSDecoder::getStream(uint32 startCode, Common::SeekableReadStream *packet) {
|
|
MPEGStream *stream = 0;
|
|
|
|
if (_streamMap.contains(startCode)) {
|
|
// We already found the stream
|
|
stream = _streamMap[startCode];
|
|
} else {
|
|
// We haven't seen this before
|
|
|
|
if (startCode == kStartCodePrivateStream1) {
|
|
PrivateStreamType streamType = detectPrivateStreamType(packet);
|
|
packet->seek(0);
|
|
|
|
// TODO: Handling of these types (as needed)
|
|
bool handled = false;
|
|
const char *typeName;
|
|
|
|
switch (streamType) {
|
|
case kPrivateStreamAC3: {
|
|
typeName = "AC-3";
|
|
|
|
#ifdef USE_A52
|
|
handled = true;
|
|
AC3AudioTrack *ac3Track = new AC3AudioTrack(*packet, _decibel, getSoundType());
|
|
stream = ac3Track;
|
|
_streamMap[startCode] = ac3Track;
|
|
addTrack(ac3Track);
|
|
#endif
|
|
break;
|
|
}
|
|
case kPrivateStreamDTS:
|
|
typeName = "DTS";
|
|
break;
|
|
case kPrivateStreamDVDPCM:
|
|
typeName = "DVD PCM";
|
|
break;
|
|
case kPrivateStreamPS2Audio: {
|
|
typeName = "PS2 Audio";
|
|
handled = true;
|
|
PS2AudioTrack *audioTrack = new PS2AudioTrack(packet, getSoundType());
|
|
stream = audioTrack;
|
|
_streamMap[startCode] = audioTrack;
|
|
addTrack(audioTrack);
|
|
break;
|
|
}
|
|
default:
|
|
typeName = "Unknown";
|
|
break;
|
|
}
|
|
|
|
if (!handled) {
|
|
warning("Unhandled DVD private stream: %s", typeName);
|
|
|
|
// Make it 0 so we don't get the warning twice
|
|
_streamMap[startCode] = 0;
|
|
}
|
|
} else if (startCode >= 0x1E0 && startCode <= 0x1EF) {
|
|
// Video stream
|
|
// TODO: Multiple video streams
|
|
warning("Found extra video stream 0x%04X", startCode);
|
|
_streamMap[startCode] = 0;
|
|
} else if (startCode >= 0x1C0 && startCode <= 0x1DF) {
|
|
#ifdef USE_MAD
|
|
// MPEG Audio stream
|
|
MPEGAudioTrack *audioTrack = new MPEGAudioTrack(*packet, getSoundType());
|
|
stream = audioTrack;
|
|
_streamMap[startCode] = audioTrack;
|
|
addTrack(audioTrack);
|
|
#else
|
|
warning("Found audio stream 0x%04X, but no MAD support compiled in", startCode);
|
|
_streamMap[startCode] = 0;
|
|
#endif
|
|
} else {
|
|
// Probably not relevant
|
|
debug(0, "Found unhandled MPEG-PS stream type 0x%04x", startCode);
|
|
_streamMap[startCode] = 0;
|
|
}
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
void MPEGPSDecoder::readNextPacket() {
|
|
for (;;) {
|
|
int32 startCode;
|
|
uint32 pts, dts;
|
|
Common::SeekableReadStream *packet = _demuxer->getNextPacket(getTime(), startCode, pts, dts);
|
|
|
|
if (!packet) {
|
|
// End of stream
|
|
for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++)
|
|
if ((*it)->getTrackType() == Track::kTrackTypeVideo)
|
|
((MPEGVideoTrack *)*it)->setEndOfTrack();
|
|
return;
|
|
}
|
|
|
|
MPEGStream *stream = getStream(startCode, packet);
|
|
|
|
if (stream) {
|
|
packet->seek(0);
|
|
|
|
bool done = stream->sendPacket(packet, pts, dts);
|
|
|
|
if (done && stream->getStreamType() == MPEGStream::kStreamTypeVideo)
|
|
return;
|
|
} else {
|
|
delete packet;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MPEGPSDecoder::addFirstVideoTrack() {
|
|
int32 startCode;
|
|
uint32 pts, dts;
|
|
Common::SeekableReadStream *packet = _demuxer->getFirstVideoPacket(startCode, pts, dts);
|
|
|
|
if (!packet)
|
|
return false;
|
|
|
|
// Video stream
|
|
// Can be MPEG-1/2 or MPEG-4/h.264. We'll assume the former and
|
|
// I hope we never need the latter.
|
|
MPEGVideoTrack *track = new MPEGVideoTrack(packet);
|
|
addTrack(track);
|
|
_streamMap[startCode] = track;
|
|
|
|
return true;
|
|
}
|
|
|
|
MPEGPSDecoder::PrivateStreamType MPEGPSDecoder::detectPrivateStreamType(Common::SeekableReadStream *packet) {
|
|
uint32 dvdCode = packet->readUint32LE();
|
|
if (packet->eos())
|
|
return kPrivateStreamUnknown;
|
|
|
|
uint32 ps2Header = packet->readUint32BE();
|
|
if (!packet->eos() && ps2Header == MKTAG('S', 'S', 'h', 'd'))
|
|
return kPrivateStreamPS2Audio;
|
|
|
|
switch (dvdCode & 0xE0) {
|
|
case 0x80:
|
|
if ((dvdCode & 0xF8) == 0x88)
|
|
return kPrivateStreamDTS;
|
|
|
|
return kPrivateStreamAC3;
|
|
case 0xA0:
|
|
return kPrivateStreamDVDPCM;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return kPrivateStreamUnknown;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Demuxer - This is the part that reads packets from the stream and delivers
|
|
// them to the decoder.
|
|
//
|
|
// It will buffer a number of packets in advance, because otherwise it may
|
|
// not encounter any audio packets until it's far too late to decode them.
|
|
// Before I added this, there would be 9 or 10 frames of video before the
|
|
// first audio packet, even though the timestamp indicated that the audio
|
|
// should start slightly before the video.
|
|
// --------------------------------------------------------------------------
|
|
|
|
#define PREBUFFERED_PACKETS 150
|
|
#define AUDIO_THRESHOLD 100
|
|
|
|
MPEGPSDecoder::MPEGPSDemuxer::MPEGPSDemuxer() {
|
|
_stream = 0;
|
|
}
|
|
|
|
MPEGPSDecoder::MPEGPSDemuxer::~MPEGPSDemuxer() {
|
|
close();
|
|
}
|
|
|
|
bool MPEGPSDecoder::MPEGPSDemuxer::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
_stream = stream;
|
|
|
|
int queuedPackets = 0;
|
|
while (queueNextPacket() && queuedPackets < PREBUFFERED_PACKETS) {
|
|
queuedPackets++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MPEGPSDecoder::MPEGPSDemuxer::close() {
|
|
delete _stream;
|
|
_stream = 0;
|
|
|
|
while (!_audioQueue.empty()) {
|
|
Packet packet = _audioQueue.pop();
|
|
delete packet._stream;
|
|
}
|
|
|
|
while (!_videoQueue.empty()) {
|
|
Packet packet = _videoQueue.pop();
|
|
delete packet._stream;
|
|
}
|
|
}
|
|
|
|
Common::SeekableReadStream *MPEGPSDecoder::MPEGPSDemuxer::getFirstVideoPacket(int32 &startCode, uint32 &pts, uint32 &dts) {
|
|
if (_videoQueue.empty())
|
|
return nullptr;
|
|
Packet packet = _videoQueue.front();
|
|
startCode = packet._startCode;
|
|
pts = packet._pts;
|
|
dts = packet._dts;
|
|
return packet._stream;
|
|
}
|
|
|
|
Common::SeekableReadStream *MPEGPSDecoder::MPEGPSDemuxer::getNextPacket(uint32 currentTime, int32 &startCode, uint32 &pts, uint32 &dts) {
|
|
queueNextPacket();
|
|
|
|
// The idea here is to prioritize the delivery of audio packets,
|
|
// because when the decoder wants a frame it will keep asking until it
|
|
// gets a frame. There is nothing like that in the decoder to ensure
|
|
// speedy delivery of audio.
|
|
|
|
if (!_audioQueue.empty()) {
|
|
Packet packet = _audioQueue.front();
|
|
bool usePacket = false;
|
|
|
|
if (packet._pts == 0xFFFFFFFF) {
|
|
// No timestamp? Use it just in case. This could be a
|
|
// bad idea, but in my tests all audio packets have a
|
|
// time stamp.
|
|
usePacket = true;
|
|
} else {
|
|
uint32 packetTime = packet._pts / 90;
|
|
if (packetTime <= currentTime || packetTime - currentTime < AUDIO_THRESHOLD || _videoQueue.empty()) {
|
|
// The packet is overdue, or will be soon.
|
|
//
|
|
// TODO: We should pad or trim the first audio
|
|
// packet based on the timestamp to get the
|
|
// audio to start at the exact desired time.
|
|
// But for some reason it seems to work well
|
|
// enough anyway. For now.
|
|
usePacket = true;
|
|
}
|
|
}
|
|
|
|
if (usePacket) {
|
|
_audioQueue.pop();
|
|
startCode = packet._startCode;
|
|
pts = packet._pts;
|
|
dts = packet._dts;
|
|
return packet._stream;
|
|
}
|
|
}
|
|
|
|
if (!_videoQueue.empty()) {
|
|
Packet packet = _videoQueue.pop();
|
|
startCode = packet._startCode;
|
|
pts = packet._pts;
|
|
dts = packet._dts;
|
|
return packet._stream;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool MPEGPSDecoder::MPEGPSDemuxer::queueNextPacket() {
|
|
if (_stream->eos())
|
|
return false;
|
|
|
|
for (;;) {
|
|
int32 startCode;
|
|
uint32 pts, dts;
|
|
int size = readNextPacketHeader(startCode, pts, dts);
|
|
|
|
if (size < 0) {
|
|
// End of stream
|
|
return false;
|
|
}
|
|
|
|
Common::SeekableReadStream *stream = _stream->readStream(size);
|
|
|
|
if (startCode == kStartCodePrivateStream1 || (startCode >= 0x1C0 && startCode <= 0x1DF)) {
|
|
// Audio packet
|
|
_audioQueue.push(Packet(stream, startCode, pts, dts));
|
|
return true;
|
|
}
|
|
|
|
if (startCode >= 0x1E0 && startCode <= 0x1EF) {
|
|
// Video packet
|
|
_videoQueue.push(Packet(stream, startCode, pts, dts));
|
|
return true;
|
|
}
|
|
|
|
delete stream;
|
|
}
|
|
}
|
|
|
|
int MPEGPSDecoder::MPEGPSDemuxer::readNextPacketHeader(int32 &startCode, uint32 &pts, uint32 &dts) {
|
|
for (;;) {
|
|
uint32 size;
|
|
startCode = findNextStartCode(size);
|
|
|
|
if (_stream->eos())
|
|
return -1;
|
|
|
|
if (startCode < 0)
|
|
continue;
|
|
|
|
uint32 lastSync = _stream->pos();
|
|
|
|
if (startCode == kStartCodePack || startCode == kStartCodeSystemHeader)
|
|
continue;
|
|
|
|
int length = _stream->readUint16BE();
|
|
|
|
if (startCode == kStartCodePaddingStream || startCode == kStartCodePrivateStream2) {
|
|
_stream->skip(length);
|
|
continue;
|
|
}
|
|
|
|
if (startCode == kStartCodeProgramStreamMap) {
|
|
parseProgramStreamMap(length);
|
|
continue;
|
|
}
|
|
|
|
// Find matching stream
|
|
if (!((startCode >= 0x1C0 && startCode <= 0x1DF) ||
|
|
(startCode >= 0x1E0 && startCode <= 0x1EF) ||
|
|
startCode == kStartCodePrivateStream1 || startCode == 0x1FD))
|
|
continue;
|
|
|
|
// Stuffing
|
|
byte c;
|
|
for (;;) {
|
|
if (length < 1) {
|
|
_stream->seek(lastSync);
|
|
continue;
|
|
}
|
|
|
|
c = _stream->readByte();
|
|
length--;
|
|
|
|
// XXX: for mpeg1, should test only bit 7
|
|
if (c != 0xFF)
|
|
break;
|
|
}
|
|
|
|
if ((c & 0xC0) == 0x40) {
|
|
// Buffer scale and size
|
|
_stream->readByte();
|
|
c = _stream->readByte();
|
|
length -= 2;
|
|
}
|
|
|
|
pts = 0xFFFFFFFF;
|
|
dts = 0xFFFFFFFF;
|
|
|
|
if ((c & 0xE0) == 0x20) {
|
|
dts = pts = readPTS(c);
|
|
length -= 4;
|
|
|
|
if (c & 0x10) {
|
|
dts = readPTS(-1);
|
|
length -= 5;
|
|
}
|
|
} else if ((c & 0xC0) == 0x80) {
|
|
// MPEG-2 PES
|
|
byte flags = _stream->readByte();
|
|
int headerLength = _stream->readByte();
|
|
length -= 2;
|
|
|
|
if (headerLength > length) {
|
|
_stream->seek(lastSync);
|
|
continue;
|
|
}
|
|
|
|
length -= headerLength;
|
|
|
|
if (flags & 0x80) {
|
|
dts = pts = readPTS(-1);
|
|
headerLength -= 5;
|
|
|
|
if (flags & 0x40) {
|
|
dts = readPTS(-1);
|
|
headerLength -= 5;
|
|
}
|
|
}
|
|
|
|
if (flags & 0x3F && headerLength == 0) {
|
|
flags &= 0xC0;
|
|
warning("Further flags set but no bytes left");
|
|
}
|
|
|
|
if (flags & 0x01) { // PES extension
|
|
byte pesExt =_stream->readByte();
|
|
headerLength--;
|
|
|
|
// Skip PES private data, program packet sequence
|
|
int skip = (pesExt >> 4) & 0xB;
|
|
skip += skip & 0x9;
|
|
|
|
if (pesExt & 0x40 || skip > headerLength) {
|
|
warning("pesExt %x is invalid", pesExt);
|
|
pesExt = skip = 0;
|
|
} else {
|
|
_stream->skip(skip);
|
|
headerLength -= skip;
|
|
}
|
|
|
|
if (pesExt & 0x01) { // PES extension 2
|
|
byte ext2Length = _stream->readByte();
|
|
headerLength--;
|
|
|
|
if ((ext2Length & 0x7F) != 0) {
|
|
byte idExt = _stream->readByte();
|
|
|
|
if ((idExt & 0x80) == 0)
|
|
startCode = (startCode & 0xFF) << 8;
|
|
|
|
headerLength--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (headerLength < 0) {
|
|
_stream->seek(lastSync);
|
|
continue;
|
|
}
|
|
|
|
_stream->skip(headerLength);
|
|
} else if (c != 0xF) {
|
|
continue;
|
|
}
|
|
|
|
if (length < 0) {
|
|
_stream->seek(lastSync);
|
|
continue;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
}
|
|
|
|
#define MAX_SYNC_SIZE 100000
|
|
|
|
int MPEGPSDecoder::MPEGPSDemuxer::findNextStartCode(uint32 &size) {
|
|
size = MAX_SYNC_SIZE;
|
|
int32 state = 0xFF;
|
|
|
|
while (size > 0) {
|
|
byte v = _stream->readByte();
|
|
|
|
if (_stream->eos())
|
|
return -1;
|
|
|
|
size--;
|
|
|
|
if (state == 0x1)
|
|
return ((state << 8) | v) & 0xFFFFFF;
|
|
|
|
state = ((state << 8) | v) & 0xFFFFFF;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
uint32 MPEGPSDecoder::MPEGPSDemuxer::readPTS(int c) {
|
|
byte buf[5];
|
|
|
|
buf[0] = (c < 0) ? _stream->readByte() : c;
|
|
_stream->read(buf + 1, 4);
|
|
|
|
return ((buf[0] & 0x0E) << 29) | ((READ_BE_UINT16(buf + 1) >> 1) << 15) | (READ_BE_UINT16(buf + 3) >> 1);
|
|
}
|
|
|
|
void MPEGPSDecoder::MPEGPSDemuxer::parseProgramStreamMap(int length) {
|
|
_stream->readByte();
|
|
_stream->readByte();
|
|
|
|
// skip program stream info
|
|
_stream->skip(_stream->readUint16BE());
|
|
|
|
int esMapLength = _stream->readUint16BE();
|
|
|
|
while (esMapLength >= 4) {
|
|
_stream->readByte(); // type
|
|
_stream->readByte(); // esID
|
|
uint16 esInfoLength = _stream->readUint16BE();
|
|
|
|
// Skip program stream info
|
|
_stream->skip(esInfoLength);
|
|
|
|
esMapLength -= 4 + esInfoLength;
|
|
}
|
|
|
|
_stream->readUint32BE(); // CRC32
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Video track
|
|
// --------------------------------------------------------------------------
|
|
|
|
MPEGPSDecoder::MPEGVideoTrack::MPEGVideoTrack(Common::SeekableReadStream *firstPacket) {
|
|
_surface = 0;
|
|
_endOfTrack = false;
|
|
_curFrame = -1;
|
|
_framePts = 0xFFFFFFFF;
|
|
_nextFrameStartTime = Audio::Timestamp(0, 27000000); // 27 MHz timer
|
|
|
|
findDimensions(firstPacket);
|
|
|
|
#ifdef USE_MPEG2
|
|
_mpegDecoder = new Image::MPEGDecoder();
|
|
#endif
|
|
}
|
|
|
|
MPEGPSDecoder::MPEGVideoTrack::~MPEGVideoTrack() {
|
|
#ifdef USE_MPEG2
|
|
delete _mpegDecoder;
|
|
#endif
|
|
|
|
if (_surface) {
|
|
_surface->free();
|
|
delete _surface;
|
|
}
|
|
}
|
|
|
|
uint16 MPEGPSDecoder::MPEGVideoTrack::getWidth() const {
|
|
return _width;
|
|
}
|
|
|
|
uint16 MPEGPSDecoder::MPEGVideoTrack::getHeight() const {
|
|
return _height;
|
|
}
|
|
|
|
Graphics::PixelFormat MPEGPSDecoder::MPEGVideoTrack::getPixelFormat() const {
|
|
return _pixelFormat;
|
|
}
|
|
|
|
bool MPEGPSDecoder::MPEGVideoTrack::setOutputPixelFormat(const Graphics::PixelFormat &format) {
|
|
_pixelFormat = format;
|
|
return true;
|
|
}
|
|
|
|
const Graphics::Surface *MPEGPSDecoder::MPEGVideoTrack::decodeNextFrame() {
|
|
return _surface;
|
|
}
|
|
|
|
bool MPEGPSDecoder::MPEGVideoTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
|
|
#ifdef USE_MPEG2
|
|
if (!_surface) {
|
|
_surface = new Graphics::Surface();
|
|
_surface->create(_width, _height, _pixelFormat);
|
|
}
|
|
|
|
if (pts != 0xFFFFFFFF) {
|
|
_framePts = pts;
|
|
}
|
|
|
|
uint32 framePeriod;
|
|
bool foundFrame = _mpegDecoder->decodePacket(*packet, framePeriod, _surface);
|
|
|
|
if (foundFrame) {
|
|
_curFrame++;
|
|
|
|
// If there has been a timestamp since the previous frame, use that for
|
|
// syncing. Usually it will be the timestamp from the current packet,
|
|
// but it might not be.
|
|
|
|
if (_framePts != 0xFFFFFFFF) {
|
|
_nextFrameStartTime = Audio::Timestamp(_framePts / 90, 27000000);
|
|
} else {
|
|
_nextFrameStartTime = _nextFrameStartTime.addFrames(framePeriod);
|
|
}
|
|
|
|
_framePts = 0xFFFFFFFF;
|
|
}
|
|
#endif
|
|
|
|
delete packet;
|
|
|
|
#ifdef USE_MPEG2
|
|
return foundFrame;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void MPEGPSDecoder::MPEGVideoTrack::findDimensions(Common::SeekableReadStream *firstPacket) {
|
|
// First, check for the picture start code
|
|
if (firstPacket->readUint32BE() != 0x1B3)
|
|
error("Failed to detect MPEG sequence start");
|
|
|
|
// This is part of the bitstream, but there's really no purpose
|
|
// to use Common::BitStream just for this: 12 bits width, 12 bits
|
|
// height
|
|
_width = firstPacket->readByte() << 4;
|
|
_height = firstPacket->readByte();
|
|
_width |= (_height & 0xF0) >> 4;
|
|
_height = ((_height & 0x0F) << 8) | firstPacket->readByte();
|
|
_pixelFormat = g_system->getScreenFormat();
|
|
if (_pixelFormat.bytesPerPixel == 1)
|
|
_pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0);
|
|
|
|
debug(0, "MPEG dimensions: %dx%d", _width, _height);
|
|
|
|
firstPacket->seek(0);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Audio track
|
|
// --------------------------------------------------------------------------
|
|
|
|
#ifdef USE_MAD
|
|
|
|
// The audio code here is almost entirely based on what we do in mp3.cpp
|
|
|
|
MPEGPSDecoder::MPEGAudioTrack::MPEGAudioTrack(Common::SeekableReadStream &firstPacket, Audio::Mixer::SoundType soundType) :
|
|
AudioTrack(soundType) {
|
|
_audStream = Audio::makePacketizedMP3Stream(firstPacket);
|
|
}
|
|
|
|
MPEGPSDecoder::MPEGAudioTrack::~MPEGAudioTrack() {
|
|
delete _audStream;
|
|
}
|
|
|
|
bool MPEGPSDecoder::MPEGAudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
|
|
_audStream->queuePacket(packet);
|
|
return true;
|
|
}
|
|
|
|
Audio::AudioStream *MPEGPSDecoder::MPEGAudioTrack::getAudioStream() const {
|
|
return _audStream;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef USE_A52
|
|
|
|
MPEGPSDecoder::AC3AudioTrack::AC3AudioTrack(Common::SeekableReadStream &firstPacket, double decibel, Audio::Mixer::SoundType soundType) :
|
|
AudioTrack(soundType) {
|
|
_audStream = Audio::makeAC3Stream(firstPacket, decibel);
|
|
if (!_audStream)
|
|
error("Could not create AC-3 stream");
|
|
}
|
|
|
|
MPEGPSDecoder::AC3AudioTrack::~AC3AudioTrack() {
|
|
delete _audStream;
|
|
}
|
|
|
|
bool MPEGPSDecoder::AC3AudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
|
|
// Skip DVD code
|
|
packet->readUint32LE();
|
|
if (packet->eos())
|
|
return true;
|
|
|
|
_audStream->queuePacket(packet);
|
|
return true;
|
|
}
|
|
|
|
Audio::AudioStream *MPEGPSDecoder::AC3AudioTrack::getAudioStream() const {
|
|
return _audStream;
|
|
}
|
|
|
|
#endif
|
|
|
|
MPEGPSDecoder::PS2AudioTrack::PS2AudioTrack(Common::SeekableReadStream *firstPacket, Audio::Mixer::SoundType soundType) :
|
|
AudioTrack(soundType) {
|
|
firstPacket->seek(12); // unknown data (4), 'SShd', header size (4)
|
|
|
|
_soundType = firstPacket->readUint32LE();
|
|
|
|
if (_soundType == PS2_ADPCM)
|
|
error("Unhandled PS2 ADPCM sound in MPEG-PS video");
|
|
else if (_soundType != PS2_PCM)
|
|
error("Unknown PS2 sound type %x", _soundType);
|
|
|
|
uint32 sampleRate = firstPacket->readUint32LE();
|
|
_channels = firstPacket->readUint32LE();
|
|
_interleave = firstPacket->readUint32LE();
|
|
|
|
byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
|
|
if (_channels == 2)
|
|
flags |= Audio::FLAG_STEREO;
|
|
|
|
_blockBuffer = new byte[_interleave * _channels];
|
|
_blockPos = _blockUsed = 0;
|
|
_audStream = Audio::makePacketizedRawStream(sampleRate, flags);
|
|
_isFirstPacket = true;
|
|
|
|
firstPacket->seek(0);
|
|
}
|
|
|
|
MPEGPSDecoder::PS2AudioTrack::~PS2AudioTrack() {
|
|
delete[] _blockBuffer;
|
|
delete _audStream;
|
|
}
|
|
|
|
bool MPEGPSDecoder::PS2AudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
|
|
packet->skip(4);
|
|
|
|
if (_isFirstPacket) {
|
|
// Skip over the header which we already parsed
|
|
packet->skip(4);
|
|
packet->skip(packet->readUint32LE());
|
|
|
|
if (packet->readUint32BE() != MKTAG('S', 'S', 'b', 'd'))
|
|
error("Failed to find 'SSbd' tag");
|
|
|
|
packet->readUint32LE(); // body size
|
|
_isFirstPacket = false;
|
|
}
|
|
|
|
uint32 size = packet->size() - packet->pos();
|
|
uint32 bytesPerChunk = _interleave * _channels;
|
|
uint32 sampleCount = calculateSampleCount(size);
|
|
|
|
byte *buffer = (byte *)malloc(sampleCount * 2);
|
|
int16 *ptr = (int16 *)buffer;
|
|
|
|
// Handle any full chunks first
|
|
while (size >= bytesPerChunk) {
|
|
packet->read(_blockBuffer + _blockPos, bytesPerChunk - _blockPos);
|
|
size -= bytesPerChunk - _blockPos;
|
|
_blockPos = 0;
|
|
|
|
for (uint32 i = _blockUsed; i < _interleave / 2; i++)
|
|
for (uint32 j = 0; j < _channels; j++)
|
|
*ptr++ = READ_UINT16(_blockBuffer + i * 2 + j * _interleave);
|
|
|
|
_blockUsed = 0;
|
|
}
|
|
|
|
// Then fallback on loading any leftover
|
|
if (size > 0) {
|
|
packet->read(_blockBuffer, size);
|
|
_blockPos = size;
|
|
|
|
if (size > (_channels - 1) * _interleave) {
|
|
_blockUsed = (size - (_channels - 1) * _interleave) / 2;
|
|
|
|
for (uint32 i = 0; i < _blockUsed; i++)
|
|
for (uint32 j = 0; j < _channels; j++)
|
|
*ptr++ = READ_UINT16(_blockBuffer + i * 2 + j * _interleave);
|
|
}
|
|
}
|
|
|
|
_audStream->queuePacket(new Common::MemoryReadStream(buffer, sampleCount * 2, DisposeAfterUse::YES));
|
|
|
|
delete packet;
|
|
return true;
|
|
}
|
|
|
|
Audio::AudioStream *MPEGPSDecoder::PS2AudioTrack::getAudioStream() const {
|
|
return _audStream;
|
|
}
|
|
|
|
uint32 MPEGPSDecoder::PS2AudioTrack::calculateSampleCount(uint32 packetSize) const {
|
|
uint32 bytesPerChunk = _interleave * _channels, result = 0;
|
|
|
|
// If we have a partial block, subtract the remainder from the size. That
|
|
// gets put towards reading the partial block
|
|
if (_blockPos != 0) {
|
|
packetSize -= bytesPerChunk - _blockPos;
|
|
result += (_interleave / 2) - _blockUsed;
|
|
}
|
|
|
|
// Round the number of whole chunks down and then calculate how many samples that gives us
|
|
result += (packetSize / bytesPerChunk) * _interleave / 2;
|
|
|
|
// Total up anything we can get from the remainder
|
|
packetSize %= bytesPerChunk;
|
|
if (packetSize > (_channels - 1) * _interleave)
|
|
result += (packetSize - (_channels - 1) * _interleave) / 2;
|
|
|
|
return result * _channels;
|
|
}
|
|
} // End of namespace Video
|