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);