mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
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:
parent
dcc2541b71
commit
558b4620e8
5 changed files with 86 additions and 34 deletions
|
@ -337,7 +337,6 @@ static void AnalyzeMpeg(u8 *buffer, MpegContext *ctx) {
|
||||||
// TODO: Does this make any sense?
|
// TODO: Does this make any sense?
|
||||||
ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, 0);
|
ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, 0);
|
||||||
}
|
}
|
||||||
ctx->mediaengine->setVideoDim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When used with scePsmf, some applications attempt to use sceMpegQueryStreamOffset
|
// When used with scePsmf, some applications attempt to use sceMpegQueryStreamOffset
|
||||||
|
|
|
@ -42,6 +42,9 @@ static const int PSMF_STREAM_SIZE_OFFSET = 0xC;
|
||||||
static const int PSMF_FIRST_TIMESTAMP_OFFSET = 0x54;
|
static const int PSMF_FIRST_TIMESTAMP_OFFSET = 0x54;
|
||||||
static const int PSMF_LAST_TIMESTAMP_OFFSET = 0x5A;
|
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 {
|
struct SceMpegAu {
|
||||||
s64_le pts; // presentation time stamp
|
s64_le pts; // presentation time stamp
|
||||||
s64_le dts; // decode time stamp
|
s64_le dts; // decode time stamp
|
||||||
|
|
|
@ -33,8 +33,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
// "Go Sudoku" is a good way to test this code...
|
// "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_AVC_STREAM = 0;
|
||||||
const int PSMF_ATRAC_STREAM = 1;
|
const int PSMF_ATRAC_STREAM = 1;
|
||||||
const int PSMF_PCM_STREAM = 2;
|
const int PSMF_PCM_STREAM = 2;
|
||||||
|
|
|
@ -168,7 +168,7 @@ void MediaEngine::closeMedia() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaEngine::DoState(PointerWrap &p) {
|
void MediaEngine::DoState(PointerWrap &p) {
|
||||||
auto s = p.Section("MediaEngine", 1, 4);
|
auto s = p.Section("MediaEngine", 1, 5);
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -181,6 +181,11 @@ void MediaEngine::DoState(PointerWrap &p) {
|
||||||
} else {
|
} else {
|
||||||
m_mpegheaderSize = sizeof(m_mpegheader);
|
m_mpegheaderSize = sizeof(m_mpegheader);
|
||||||
}
|
}
|
||||||
|
if (s >= 5) {
|
||||||
|
p.Do(m_mpegheaderReadPos);
|
||||||
|
} else {
|
||||||
|
m_mpegheaderReadPos = m_mpegheaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
p.Do(m_ringbuffersize);
|
p.Do(m_ringbuffersize);
|
||||||
|
|
||||||
|
@ -194,8 +199,6 @@ void MediaEngine::DoState(PointerWrap &p) {
|
||||||
u32 hasopencontext = false;
|
u32 hasopencontext = false;
|
||||||
#endif
|
#endif
|
||||||
p.Do(hasopencontext);
|
p.Do(hasopencontext);
|
||||||
if (hasopencontext && p.mode == p.MODE_READ)
|
|
||||||
openContext();
|
|
||||||
if (m_pdata)
|
if (m_pdata)
|
||||||
m_pdata->DoState(p);
|
m_pdata->DoState(p);
|
||||||
if (m_demux)
|
if (m_demux)
|
||||||
|
@ -209,6 +212,10 @@ void MediaEngine::DoState(PointerWrap &p) {
|
||||||
p.Do(m_lastTimeStamp);
|
p.Do(m_lastTimeStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasopencontext && p.mode == p.MODE_READ) {
|
||||||
|
openContext(true);
|
||||||
|
}
|
||||||
|
|
||||||
p.Do(m_isVideoEnd);
|
p.Do(m_isVideoEnd);
|
||||||
bool noAudioDataRemoved;
|
bool noAudioDataRemoved;
|
||||||
p.Do(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;
|
MediaEngine *mpeg = (MediaEngine *)opaque;
|
||||||
|
|
||||||
int size = buf_size;
|
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);
|
size = std::min(buf_size, mpeg->m_mpegheaderSize - mpeg->m_mpegheaderReadPos);
|
||||||
memcpy(buf, mpeg->m_mpegheader + mpeg->m_mpegheaderReadPos, size);
|
memcpy(buf, mpeg->m_mpegheader + mpeg->m_mpegheaderReadPos, size);
|
||||||
mpeg->m_mpegheaderReadPos += size;
|
mpeg->m_mpegheaderReadPos += size;
|
||||||
} else if (mpeg->m_mpegheaderReadPos == mpeg->m_mpegheaderSize) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
} else {
|
||||||
size = mpeg->m_pdata->pop_front(buf, buf_size);
|
size = mpeg->m_pdata->pop_front(buf, buf_size);
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
|
@ -238,34 +242,74 @@ int _MpegReadbuffer(void *opaque, uint8_t *buf, int buf_size)
|
||||||
return 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
|
#ifdef USE_FFMPEG
|
||||||
InitFFmpeg();
|
InitFFmpeg();
|
||||||
|
|
||||||
if (m_pFormatCtx || !m_pdata)
|
if (m_pFormatCtx || !m_pdata)
|
||||||
return false;
|
return false;
|
||||||
|
if (!keepReadPos) {
|
||||||
m_mpegheaderReadPos = 0;
|
m_mpegheaderReadPos = 0;
|
||||||
|
}
|
||||||
m_decodingsize = 0;
|
m_decodingsize = 0;
|
||||||
|
|
||||||
|
m_bufSize = std::max(m_bufSize, m_mpegheaderSize);
|
||||||
u8 *tempbuf = (u8*)av_malloc(m_bufSize);
|
u8 *tempbuf = (u8*)av_malloc(m_bufSize);
|
||||||
|
|
||||||
m_pFormatCtx = avformat_alloc_context();
|
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;
|
m_pFormatCtx->pb = m_pIOContext;
|
||||||
|
|
||||||
// Open video file
|
// Open video file
|
||||||
AVDictionary *open_opt = nullptr;
|
AVDictionary *open_opt = nullptr;
|
||||||
av_dict_set_int(&open_opt, "probesize", m_mpegheaderSize, 0);
|
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);
|
av_dict_free(&open_opt);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
av_dict_free(&open_opt);
|
av_dict_free(&open_opt);
|
||||||
|
|
||||||
|
if (!SetupStreams()) {
|
||||||
|
// Fallback to old behavior.
|
||||||
if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0) {
|
if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0) {
|
||||||
closeContext();
|
closeContext();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_videoStream >= (int)m_pFormatCtx->nb_streams) {
|
if (m_videoStream >= (int)m_pFormatCtx->nb_streams) {
|
||||||
WARN_LOG_REPORT(ME, "Bad video stream %d", m_videoStream);
|
WARN_LOG_REPORT(ME, "Bad video stream %d", m_videoStream);
|
||||||
|
@ -290,8 +334,6 @@ bool MediaEngine::openContext() {
|
||||||
setVideoDim();
|
setVideoDim();
|
||||||
m_audioContext = new SimpleAudio(m_audioType, 44100, 2);
|
m_audioContext = new SimpleAudio(m_audioType, 44100, 2);
|
||||||
m_isVideoEnd = false;
|
m_isVideoEnd = false;
|
||||||
m_mpegheaderReadPos++;
|
|
||||||
av_seek_frame(m_pFormatCtx, m_videoStream, 0, 0);
|
|
||||||
#endif // USE_FFMPEG
|
#endif // USE_FFMPEG
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -354,8 +396,7 @@ int MediaEngine::addStreamData(const u8 *buffer, int addSize) {
|
||||||
#ifdef USE_FFMPEG
|
#ifdef USE_FFMPEG
|
||||||
if (!m_pFormatCtx && m_pdata->getQueueSize() >= 2048) {
|
if (!m_pFormatCtx && m_pdata->getQueueSize() >= 2048) {
|
||||||
m_mpegheaderSize = m_pdata->get_front(m_mpegheader, sizeof(m_mpegheader));
|
m_mpegheaderSize = m_pdata->get_front(m_mpegheader, sizeof(m_mpegheader));
|
||||||
int mpegoffset = (int)(*(s32_be*)(m_mpegheader + 8));
|
m_pdata->pop_front(0, m_mpegheaderSize);
|
||||||
m_pdata->pop_front(0, mpegoffset);
|
|
||||||
openContext();
|
openContext();
|
||||||
}
|
}
|
||||||
#endif // USE_FFMPEG
|
#endif // USE_FFMPEG
|
||||||
|
@ -418,8 +459,7 @@ bool MediaEngine::setVideoStream(int streamNum, bool force) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open codec
|
// Open codec
|
||||||
AVDictionary *optionsDict = 0;
|
if (avcodec_open2(m_pCodecCtx, pCodec, nullptr) < 0) {
|
||||||
if (avcodec_open2(m_pCodecCtx, pCodec, &optionsDict) < 0) {
|
|
||||||
return false; // Could not open codec
|
return false; // Could not open codec
|
||||||
}
|
}
|
||||||
m_pCodecCtxs[streamNum] = m_pCodecCtx;
|
m_pCodecCtxs[streamNum] = m_pCodecCtx;
|
||||||
|
@ -451,11 +491,19 @@ bool MediaEngine::setVideoDim(int width, int height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate video frame
|
// Allocate video frame
|
||||||
|
if (!m_pFrame) {
|
||||||
m_pFrame = av_frame_alloc();
|
m_pFrame = av_frame_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
sws_freeContext(m_sws_ctx);
|
sws_freeContext(m_sws_ctx);
|
||||||
m_sws_ctx = NULL;
|
m_sws_ctx = NULL;
|
||||||
m_sws_fmt = -1;
|
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);
|
updateSwsFormat(GE_CMODE_32BIT_ABGR8888);
|
||||||
|
|
||||||
// Allocate video frame for RGB24
|
// Allocate video frame for RGB24
|
||||||
|
@ -523,14 +571,9 @@ bool MediaEngine::stepVideo(int videoPixelMode, bool skipFrame) {
|
||||||
return false;
|
return false;
|
||||||
if (!m_pCodecCtx)
|
if (!m_pCodecCtx)
|
||||||
return false;
|
return false;
|
||||||
if ((!m_pFrame)||(!m_pFrameRGB))
|
if (!m_pFrame)
|
||||||
return false;
|
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;
|
AVPacket packet;
|
||||||
av_init_packet(&packet);
|
av_init_packet(&packet);
|
||||||
int frameFinished;
|
int frameFinished;
|
||||||
|
@ -551,7 +594,15 @@ bool MediaEngine::stepVideo(int videoPixelMode, bool skipFrame) {
|
||||||
|
|
||||||
int result = avcodec_decode_video2(m_pCodecCtx, m_pFrame, &frameFinished, &packet);
|
int result = avcodec_decode_video2(m_pCodecCtx, m_pFrame, &frameFinished, &packet);
|
||||||
if (frameFinished) {
|
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,
|
sws_scale(m_sws_ctx, m_pFrame->data, m_pFrame->linesize, 0,
|
||||||
m_pCodecCtx->height, m_pFrameRGB->data, m_pFrameRGB->linesize);
|
m_pCodecCtx->height, m_pFrameRGB->data, m_pFrameRGB->linesize);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ public:
|
||||||
bool loadStream(const u8 *buffer, int readSize, int RingbufferSize);
|
bool loadStream(const u8 *buffer, int readSize, int RingbufferSize);
|
||||||
bool reloadStream();
|
bool reloadStream();
|
||||||
// open the mpeg context
|
// open the mpeg context
|
||||||
bool openContext();
|
bool openContext(bool keepReadPos = false);
|
||||||
void closeContext();
|
void closeContext();
|
||||||
|
|
||||||
// Returns number of packets actually added. I guess the buffer might be full.
|
// 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 xpos, int ypos, int width, int height);
|
||||||
int getAudioSamples(u32 bufferPtr);
|
int getAudioSamples(u32 bufferPtr);
|
||||||
|
|
||||||
bool setVideoDim(int width = 0, int height = 0);
|
|
||||||
s64 getVideoTimeStamp();
|
s64 getVideoTimeStamp();
|
||||||
s64 getAudioTimeStamp();
|
s64 getAudioTimeStamp();
|
||||||
s64 getLastTimeStamp();
|
s64 getLastTimeStamp();
|
||||||
|
@ -94,6 +93,8 @@ public:
|
||||||
void DoState(PointerWrap &p);
|
void DoState(PointerWrap &p);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool SetupStreams();
|
||||||
|
bool setVideoDim(int width = 0, int height = 0);
|
||||||
void updateSwsFormat(int videoPixelMode);
|
void updateSwsFormat(int videoPixelMode);
|
||||||
int getNextAudioFrame(u8 **buf, int *headerCode1, int *headerCode2);
|
int getNextAudioFrame(u8 **buf, int *headerCode1, int *headerCode2);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue