Skip to content

Commit

Permalink
Mpeg: Parse video streams from PSMF header.
Browse files Browse the repository at this point in the history
Without doing this, FFmpeg will try to probe the streams to detect them
instead.  When it does this, sometimes it tries to read beyond the data
that's available - and then gets confused by EOFs.

Parsing this way allows us to control the situation.

An example is Valkyrie Profile, corruption in the first frames of the
second video during the intro.  Thi doesn't fix it yet, but now it's just
a matter of buffering.
  • Loading branch information
unknownbrackets committed Jul 24, 2016
1 parent dcc2541 commit 558b462
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 34 deletions.
1 change: 0 additions & 1 deletion Core/HLE/sceMpeg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,6 @@ static void AnalyzeMpeg(u8 *buffer, MpegContext *ctx) {
// TODO: Does this make any sense?
ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, 0);
}
ctx->mediaengine->setVideoDim();
}

// When used with scePsmf, some applications attempt to use sceMpegQueryStreamOffset
Expand Down
3 changes: 3 additions & 0 deletions Core/HLE/sceMpeg.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ static const int PSMF_STREAM_SIZE_OFFSET = 0xC;
static const int PSMF_FIRST_TIMESTAMP_OFFSET = 0x54;
static const int PSMF_LAST_TIMESTAMP_OFFSET = 0x5A;

static const int PSMF_VIDEO_STREAM_ID = 0xE0;
static const int PSMF_AUDIO_STREAM_ID = 0xBD;

struct SceMpegAu {
s64_le pts; // presentation time stamp
s64_le dts; // decode time stamp
Expand Down
2 changes: 0 additions & 2 deletions Core/HLE/scePsmf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
#include <algorithm>

// "Go Sudoku" is a good way to test this code...
const int PSMF_VIDEO_STREAM_ID = 0xE0;
const int PSMF_AUDIO_STREAM_ID = 0xBD;
const int PSMF_AVC_STREAM = 0;
const int PSMF_ATRAC_STREAM = 1;
const int PSMF_PCM_STREAM = 2;
Expand Down
109 changes: 80 additions & 29 deletions Core/HW/MediaEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ void MediaEngine::closeMedia() {
}

void MediaEngine::DoState(PointerWrap &p) {
auto s = p.Section("MediaEngine", 1, 4);
auto s = p.Section("MediaEngine", 1, 5);
if (!s)
return;

Expand All @@ -181,6 +181,11 @@ void MediaEngine::DoState(PointerWrap &p) {
} else {
m_mpegheaderSize = sizeof(m_mpegheader);
}
if (s >= 5) {
p.Do(m_mpegheaderReadPos);
} else {
m_mpegheaderReadPos = m_mpegheaderSize;
}

p.Do(m_ringbuffersize);

Expand All @@ -194,8 +199,6 @@ void MediaEngine::DoState(PointerWrap &p) {
u32 hasopencontext = false;
#endif
p.Do(hasopencontext);
if (hasopencontext && p.mode == p.MODE_READ)
openContext();
if (m_pdata)
m_pdata->DoState(p);
if (m_demux)
Expand All @@ -209,6 +212,10 @@ void MediaEngine::DoState(PointerWrap &p) {
p.Do(m_lastTimeStamp);
}

if (hasopencontext && p.mode == p.MODE_READ) {
openContext(true);
}

p.Do(m_isVideoEnd);
bool noAudioDataRemoved;
p.Do(noAudioDataRemoved);
Expand All @@ -219,17 +226,14 @@ void MediaEngine::DoState(PointerWrap &p) {
}
}

int _MpegReadbuffer(void *opaque, uint8_t *buf, int buf_size)
{
static int MpegReadbuffer(void *opaque, uint8_t *buf, int buf_size) {
MediaEngine *mpeg = (MediaEngine *)opaque;

int size = buf_size;
if (mpeg->m_mpegheaderReadPos < mpeg->m_mpegheaderSize) {
size = std::min(buf_size, mpeg->m_mpegheaderSize - mpeg->m_mpegheaderReadPos);
memcpy(buf, mpeg->m_mpegheader + mpeg->m_mpegheaderReadPos, size);
mpeg->m_mpegheaderReadPos += size;
} else if (mpeg->m_mpegheaderReadPos == mpeg->m_mpegheaderSize) {
return 0;
} else {
size = mpeg->m_pdata->pop_front(buf, buf_size);
if (size > 0)
Expand All @@ -238,33 +242,73 @@ int _MpegReadbuffer(void *opaque, uint8_t *buf, int buf_size)
return size;
}

bool MediaEngine::openContext() {
bool MediaEngine::SetupStreams() {
#ifdef USE_FFMPEG
const u32 magic = *(u32_le *)&m_mpegheader[0];
if (magic != PSMF_MAGIC) {
WARN_LOG_REPORT(ME, "Could not setup streams, bad magic: %08x", magic);
return false;
}
int numStreams = *(u16_be *)&m_mpegheader[0x80];
if (numStreams <= 0 || numStreams > 8) {
// Looks crazy. Let's bail out and let FFmpeg handle it.
WARN_LOG_REPORT(ME, "Could not setup streams, unexpected stream count: %d", numStreams);
return false;
}

// Looking good. Let's add those streams.
const AVCodec *h264_codec = avcodec_find_decoder(AV_CODEC_ID_H264);
for (int i = 0; i < numStreams; i++) {
const u8 *const currentStreamAddr = m_mpegheader + 0x82 + i * 16;
int streamId = currentStreamAddr[0];

// We only set video streams. We demux the audio stream separately.
if ((streamId & PSMF_VIDEO_STREAM_ID) == PSMF_VIDEO_STREAM_ID) {
AVStream *stream = avformat_new_stream(m_pFormatCtx, h264_codec);
stream->id = 0x00000100 | streamId;
stream->request_probe = 0;
stream->need_parsing = AVSTREAM_PARSE_FULL;
// We could set the width here, but we don't need to.
}
}

#endif
return true;
}

bool MediaEngine::openContext(bool keepReadPos) {
#ifdef USE_FFMPEG
InitFFmpeg();

if (m_pFormatCtx || !m_pdata)
return false;
m_mpegheaderReadPos = 0;
if (!keepReadPos) {
m_mpegheaderReadPos = 0;
}
m_decodingsize = 0;

u8* tempbuf = (u8*)av_malloc(m_bufSize);
m_bufSize = std::max(m_bufSize, m_mpegheaderSize);
u8 *tempbuf = (u8*)av_malloc(m_bufSize);

m_pFormatCtx = avformat_alloc_context();
m_pIOContext = avio_alloc_context(tempbuf, m_bufSize, 0, (void*)this, _MpegReadbuffer, NULL, 0);
m_pIOContext = avio_alloc_context(tempbuf, m_bufSize, 0, (void*)this, &MpegReadbuffer, nullptr, nullptr);
m_pFormatCtx->pb = m_pIOContext;

// Open video file
AVDictionary *open_opt = nullptr;
av_dict_set_int(&open_opt, "probesize", m_mpegheaderSize, 0);
if (avformat_open_input((AVFormatContext**)&m_pFormatCtx, NULL, NULL, &open_opt) != 0) {
if (avformat_open_input((AVFormatContext**)&m_pFormatCtx, nullptr, nullptr, &open_opt) != 0) {
av_dict_free(&open_opt);
return false;
}
av_dict_free(&open_opt);

if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0) {
closeContext();
return false;
if (!SetupStreams()) {
// Fallback to old behavior.
if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0) {
closeContext();
return false;
}
}

if (m_videoStream >= (int)m_pFormatCtx->nb_streams) {
Expand All @@ -290,8 +334,6 @@ bool MediaEngine::openContext() {
setVideoDim();
m_audioContext = new SimpleAudio(m_audioType, 44100, 2);
m_isVideoEnd = false;
m_mpegheaderReadPos++;
av_seek_frame(m_pFormatCtx, m_videoStream, 0, 0);
#endif // USE_FFMPEG
return true;
}
Expand Down Expand Up @@ -354,8 +396,7 @@ int MediaEngine::addStreamData(const u8 *buffer, int addSize) {
#ifdef USE_FFMPEG
if (!m_pFormatCtx && m_pdata->getQueueSize() >= 2048) {
m_mpegheaderSize = m_pdata->get_front(m_mpegheader, sizeof(m_mpegheader));
int mpegoffset = (int)(*(s32_be*)(m_mpegheader + 8));
m_pdata->pop_front(0, mpegoffset);
m_pdata->pop_front(0, m_mpegheaderSize);
openContext();
}
#endif // USE_FFMPEG
Expand Down Expand Up @@ -418,8 +459,7 @@ bool MediaEngine::setVideoStream(int streamNum, bool force) {
}

// Open codec
AVDictionary *optionsDict = 0;
if (avcodec_open2(m_pCodecCtx, pCodec, &optionsDict) < 0) {
if (avcodec_open2(m_pCodecCtx, pCodec, nullptr) < 0) {
return false; // Could not open codec
}
m_pCodecCtxs[streamNum] = m_pCodecCtx;
Expand Down Expand Up @@ -451,11 +491,19 @@ bool MediaEngine::setVideoDim(int width, int height)
}

// Allocate video frame
m_pFrame = av_frame_alloc();
if (!m_pFrame) {
m_pFrame = av_frame_alloc();
}

sws_freeContext(m_sws_ctx);
m_sws_ctx = NULL;
m_sws_fmt = -1;

if (m_desWidth == 0 || m_desHeight == 0) {
// Can't setup SWS yet, so stop for now.
return false;
}

updateSwsFormat(GE_CMODE_32BIT_ABGR8888);

// Allocate video frame for RGB24
Expand Down Expand Up @@ -523,14 +571,9 @@ bool MediaEngine::stepVideo(int videoPixelMode, bool skipFrame) {
return false;
if (!m_pCodecCtx)
return false;
if ((!m_pFrame)||(!m_pFrameRGB))
if (!m_pFrame)
return false;

updateSwsFormat(videoPixelMode);
// TODO: Technically we could set this to frameWidth instead of m_desWidth for better perf.
// Update the linesize for the new format too. We started with the largest size, so it should fit.
m_pFrameRGB->linesize[0] = getPixelFormatBytes(videoPixelMode) * m_desWidth;

AVPacket packet;
av_init_packet(&packet);
int frameFinished;
Expand All @@ -551,7 +594,15 @@ bool MediaEngine::stepVideo(int videoPixelMode, bool skipFrame) {

int result = avcodec_decode_video2(m_pCodecCtx, m_pFrame, &frameFinished, &packet);
if (frameFinished) {
if (!skipFrame) {
if (!m_pFrameRGB) {
setVideoDim();
}
if (m_pFrameRGB && !skipFrame) {
updateSwsFormat(videoPixelMode);
// TODO: Technically we could set this to frameWidth instead of m_desWidth for better perf.
// Update the linesize for the new format too. We started with the largest size, so it should fit.
m_pFrameRGB->linesize[0] = getPixelFormatBytes(videoPixelMode) * m_desWidth;

sws_scale(m_sws_ctx, m_pFrame->data, m_pFrame->linesize, 0,
m_pCodecCtx->height, m_pFrameRGB->data, m_pFrameRGB->linesize);
}
Expand Down
5 changes: 3 additions & 2 deletions Core/HW/MediaEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class MediaEngine
bool loadStream(const u8 *buffer, int readSize, int RingbufferSize);
bool reloadStream();
// open the mpeg context
bool openContext();
bool openContext(bool keepReadPos = false);
void closeContext();

// Returns number of packets actually added. I guess the buffer might be full.
Expand All @@ -81,7 +81,6 @@ class MediaEngine
int xpos, int ypos, int width, int height);
int getAudioSamples(u32 bufferPtr);

bool setVideoDim(int width = 0, int height = 0);
s64 getVideoTimeStamp();
s64 getAudioTimeStamp();
s64 getLastTimeStamp();
Expand All @@ -94,6 +93,8 @@ class MediaEngine
void DoState(PointerWrap &p);

private:
bool SetupStreams();
bool setVideoDim(int width = 0, int height = 0);
void updateSwsFormat(int videoPixelMode);
int getNextAudioFrame(u8 **buf, int *headerCode1, int *headerCode2);

Expand Down

0 comments on commit 558b462

Please sign in to comment.