From 558b4620e8643ef46c1bf0d45cba5eae75e26860 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 5 Jun 2016 17:54:23 -0700 Subject: [PATCH] Mpeg: Parse video streams from PSMF header. 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. --- Core/HLE/sceMpeg.cpp | 1 - Core/HLE/sceMpeg.h | 3 ++ Core/HLE/scePsmf.cpp | 2 - Core/HW/MediaEngine.cpp | 109 +++++++++++++++++++++++++++++----------- Core/HW/MediaEngine.h | 5 +- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index add9a722a5c7..f2a6feda619c 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -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 diff --git a/Core/HLE/sceMpeg.h b/Core/HLE/sceMpeg.h index b4a173d70503..013e5a7b295c 100644 --- a/Core/HLE/sceMpeg.h +++ b/Core/HLE/sceMpeg.h @@ -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 diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index 45945a435713..62bfd19850cf 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -33,8 +33,6 @@ #include // "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; diff --git a/Core/HW/MediaEngine.cpp b/Core/HW/MediaEngine.cpp index e0b47b74e383..ba57de76549e 100644 --- a/Core/HW/MediaEngine.cpp +++ b/Core/HW/MediaEngine.cpp @@ -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; @@ -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); @@ -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) @@ -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); @@ -219,8 +226,7 @@ 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; @@ -228,8 +234,6 @@ int _MpegReadbuffer(void *opaque, uint8_t *buf, int buf_size) 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) @@ -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) { @@ -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; } @@ -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 @@ -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; @@ -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 @@ -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; @@ -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); } diff --git a/Core/HW/MediaEngine.h b/Core/HW/MediaEngine.h index fc1f80ef43bf..6561176b03af 100644 --- a/Core/HW/MediaEngine.h +++ b/Core/HW/MediaEngine.h @@ -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. @@ -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(); @@ -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);