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.
This commit is contained in:
Unknown W. Brackets 2016-06-05 17:54:23 -07:00
parent dcc2541b71
commit 558b4620e8
5 changed files with 86 additions and 34 deletions

View File

@ -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

View File

@ -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

View File

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

View File

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

View File

@ -60,7 +60,7 @@ public:
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 @@ public:
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 @@ public:
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);