mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-23 13:30:02 +00: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
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user