// Program signals that it has written data to the ringbuffer and gets a callback ? u32 sceMpegRingbufferPut(u32 ringbufferAddr, u32 numPackets, u32 available) { DEBUG_LOG(HLE, "sceMpegRingbufferPut(%08x, %i, %i)", ringbufferAddr, numPackets, available); numPackets = std::min(numPackets, available); if (numPackets <= 0) return 0; SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ringbufferAddr, &ringbuffer); MpegContext *ctx = getMpegCtx(ringbuffer.mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegRingbufferPut(%08x, %i, %i): bad mpeg handle %08x", ringbufferAddr, numPackets, available, ringbuffer.mpeg); return 0; } // Execute callback function as a direct MipsCall, no blocking here so no messing around with wait states etc if (ringbuffer.callback_addr) { PostPutAction *action = (PostPutAction *)__KernelCreateAction(actionPostPut); action->setRingAddr(ringbufferAddr); // TODO: Should call this multiple times until we get numPackets. // Normally this would be if it did not read enough, but also if available > packets. // Should ultimately return the TOTAL number of returned packets. u32 packetsThisRound = std::min(numPackets, (u32)ringbuffer.packets); u32 args[3] = {(u32)ringbuffer.data, packetsThisRound, (u32)ringbuffer.callback_args}; __KernelDirectMipsCall(ringbuffer.callback_addr, action, args, 3, false); } else { ERROR_LOG(HLE, "sceMpegRingbufferPut: callback_addr zero"); } return 0; }
int sceMpegInitAu(u32 mpeg, u32 bufferAddr, u32 auPointer) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegInitAu(%08x, %i, %08x): bad mpeg handle", mpeg, bufferAddr, auPointer); return -1; } DEBUG_LOG(HLE, "sceMpegInitAu(%08x, %i, %08x)", mpeg, bufferAddr, auPointer); SceMpegAu sceAu; sceAu.read(auPointer); if (bufferAddr >= 1 && bufferAddr <= (u32)NUM_ES_BUFFERS && ctx->esBuffers[bufferAddr - 1]) { // This esbuffer has been allocated for Avc. sceAu.esBuffer = bufferAddr; // Can this be right??? not much of a buffer pointer.. sceAu.esSize = MPEG_AVC_ES_SIZE; sceAu.dts = 0; sceAu.pts = 0; sceAu.write(auPointer); } else { // This esbuffer has been left as Atrac. sceAu.esBuffer = bufferAddr; sceAu.esSize = MPEG_ATRAC_ES_SIZE; sceAu.pts = 0; sceAu.dts = UNKNOWN_TIMESTAMP; sceAu.write(auPointer); } return 0; }
void PostPutAction::run(MipsCall &call) { SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ringAddr_, &ringbuffer); MpegContext *ctx = getMpegCtx(ringbuffer.mpeg); int packetsAdded = currentMIPS->r[2]; if (packetsAdded > 0) { if (packetsAdded > ringbuffer.packetsFree) { WARN_LOG(HLE, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAdded, ringbuffer.packetsFree); packetsAdded = ringbuffer.packetsFree; } int actuallyAdded = ctx->mediaengine->addStreamData(Memory::GetPointer(ringbuffer.data), packetsAdded * 2048) / 2048; if (actuallyAdded != packetsAdded) { WARN_LOG_REPORT(HLE, "sceMpegRingbufferPut(): unable to enqueue all added packets, going to overwrite some frames."); } ringbuffer.packetsRead += packetsAdded; ringbuffer.packetsWritten += packetsAdded; ringbuffer.packetsFree -= packetsAdded; } DEBUG_LOG(HLE, "packetAdded: %i packetsRead: %i packetsTotal: %i", packetsAdded, ringbuffer.packetsRead, ringbuffer.packets); Memory::WriteStruct(ringAddr_, &ringbuffer); call.setReturnValue(packetsAdded); }
int sceMpegAvcDecodeDetail(u32 mpeg, u32 detailAddr) { if (!Memory::IsValidAddress(detailAddr)) { WARN_LOG(HLE, "sceMpegAvcDecodeDetail(%08x, %08x): invalid detailAddr", mpeg, detailAddr); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegAvcDecodeDetail(%08x, %08x): bad mpeg handle", mpeg, detailAddr); return -1; } DEBUG_LOG(HLE, "sceMpegAvcDecodeDetail(%08x, %08x)", mpeg, detailAddr); Memory::Write_U32(ctx->avc.avcDecodeResult, detailAddr + 0); Memory::Write_U32(ctx->videoFrameCount, detailAddr + 4); Memory::Write_U32(ctx->avc.avcDetailFrameWidth, detailAddr + 8); Memory::Write_U32(ctx->avc.avcDetailFrameHeight, detailAddr + 12); Memory::Write_U32(0, detailAddr + 16); Memory::Write_U32(0, detailAddr + 20); Memory::Write_U32(0, detailAddr + 24); Memory::Write_U32(0, detailAddr + 28); Memory::Write_U32(ctx->avc.avcFrameStatus, detailAddr + 32); return 0; }
int sceMpegQueryStreamOffset(u32 mpeg, u32 bufferAddr, u32 offsetAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegQueryStreamOffset(%08x, %08x, %08x): bad mpeg handle", mpeg, bufferAddr, offsetAddr); return -1; } DEBUG_LOG(HLE, "sceMpegQueryStreamOffset(%08x, %08x, %08x)", mpeg, bufferAddr, offsetAddr); // Kinda destructive, no? AnalyzeMpeg(bufferAddr, ctx); if (ctx->mpegMagic != PSMF_MAGIC) { ERROR_LOG(HLE, "sceMpegQueryStreamOffset: Bad PSMF magic"); Memory::Write_U32(0, offsetAddr); return ERROR_MPEG_INVALID_VALUE; } else if (ctx->mpegVersion < 0) { ERROR_LOG(HLE, "sceMpegQueryStreamOffset: Bad version"); Memory::Write_U32(0, offsetAddr); return ERROR_MPEG_BAD_VERSION; } else if ((ctx->mpegOffset & 2047) != 0 || ctx->mpegOffset == 0) { ERROR_LOG(HLE, "sceMpegQueryStreamOffset: Bad offset"); Memory::Write_U32(0, offsetAddr); return ERROR_MPEG_INVALID_VALUE; } Memory::Write_U32(ctx->mpegOffset, offsetAddr); return 0; }
u32 sceMpegAtracDecode(u32 mpeg, u32 auAddr, u32 bufferAddr, int init) { DEBUG_LOG(HLE, "UNIMPL sceMpegAtracDecode(%08x, %08x, %08x, %i)", mpeg, auAddr, bufferAddr, init); MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { return 0; } if (!Memory::IsValidAddress(auAddr) || !Memory::IsValidAddress(bufferAddr)) { ERROR_LOG(HLE, "sceMpegAtracDecode: bad addresses"); return 0; } SceMpegAu avcAu; avcAu.read(auAddr); Memory::Memset(bufferAddr, 0, MPEG_ATRAC_ES_OUTPUT_SIZE); ctx->mediaengine->getAudioSamples(Memory::GetPointer(bufferAddr)); avcAu.pts = ctx->mediaengine->getAudioTimeStamp(); avcAu.write(auAddr); return hleDelayResult(0, "mpeg atrac decode", atracDecodeDelayMs); //hleEatMicro(4000); //return hleDelayResult(0, "mpeg atrac decode", 200); }
u32 sceMpegAvcDecodeFlush(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); ERROR_LOG(HLE, "UNIMPL sceMpegAvcDecodeFlush(%08x)", mpeg); if ( ctx->videoFrameCount > 0 || ctx->audioFrameCount > 0) { //__MpegFinish(); } return 0; }
int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, auAddr, bufferAddr, initAddr); return 0; } if (!Memory::IsValidAddress(auAddr) || !Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(initAddr)) { ERROR_LOG(HLE, "sceMpegAvcDecodeYCbCr: bad addresses"); return 0; } SceMpegAu avcAu; avcAu.read(auAddr); SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ctx->mpegRingbufferAddr, &ringbuffer); if (ringbuffer.packetsRead == 0) { // empty! return hleDelayResult(MPEG_AVC_DECODE_ERROR_FATAL, "mpeg buffer empty", avcEmptyDelayMs); } u32 buffer = Memory::Read_U32(bufferAddr); u32 init = Memory::Read_U32(initAddr); DEBUG_LOG(HLE, "*buffer = %08x, *init = %08x", buffer, init); if (ctx->mediaengine->stepVideo(ctx->videoPixelMode)) { // do nothing ; } ringbuffer.packetsFree = std::max(0, ringbuffer.packets - ctx->mediaengine->getBufferedSize() / 2048); avcAu.pts = ctx->mediaengine->getVideoTimeStamp(); ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; // Flush structs back to memory avcAu.write(auAddr); Memory::WriteStruct(ctx->mpegRingbufferAddr, &ringbuffer); Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); // 1 = showing, 0 = not showing DEBUG_LOG(HLE, "UNIMPL sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x)", mpeg, auAddr, bufferAddr, initAddr); if (ctx->videoFrameCount <= 1) return hleDelayResult(0, "mpeg decode", avcFirstDelayMs); else return hleDelayResult(0, "mpeg decode", avcDecodeDelayMs); //hleEatMicro(3300); //return hleDelayResult(0, "mpeg decode", 200); }
int sceMpegGetAtracAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegGetAtracAu(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, streamId, auAddr, attrAddr); return -1; } SceMpegRingBuffer mpegRingbuffer; Memory::ReadStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); SceMpegAu sceAu; sceAu.read(auAddr); auto streamInfo = ctx->streamMap.find(streamId); if (streamInfo != ctx->streamMap.end() && streamInfo->second.needsReset) { sceAu.pts = 0; streamInfo->second.needsReset = false; } int result = 0; if (mpegRingbuffer.packetsFree == mpegRingbuffer.packets) { DEBUG_LOG(HLE, "PSP_ERROR_MPEG_NO_DATA=sceMpegGetAtracAu(%08x, %08x, %08x, %08x)", mpeg, streamId, auAddr, attrAddr); // TODO: Does this really delay? return hleDelayResult(PSP_ERROR_MPEG_NO_DATA, "mpeg get atrac", mpegDecodeErrorDelayMs); } //... // TODO: Just faking it. sceAu.pts += videoTimestampStep; sceAu.write(auAddr); // TODO: And also audio end? if (ctx->endOfVideoReached) { if (mpegRingbuffer.packetsFree < mpegRingbuffer.packets) { mpegRingbuffer.packetsFree = mpegRingbuffer.packets; Memory::WriteStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); } result = PSP_ERROR_MPEG_NO_DATA; } if (Memory::IsValidAddress(attrAddr)) { Memory::Write_U32(0, attrAddr); } DEBUG_LOG(HLE, "%x=sceMpegGetAtracAu(%08x, %08x, %08x, %08x)", result, mpeg, streamId, auAddr, attrAddr); // TODO: Not clear on exactly when this delays. return hleDelayResult(result, "mpeg get atrac", 100); }
int sceMpegRingbufferAvailableSize(u32 ringbufferAddr) { if (!Memory::IsValidAddress(ringbufferAddr)) { ERROR_LOG(HLE, "sceMpegRingbufferAvailableSize(%08x) - bad address", ringbufferAddr); return -1; } SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ringbufferAddr, &ringbuffer); DEBUG_LOG(HLE, "%i=sceMpegRingbufferAvailableSize(%08x)", ringbuffer.packetsFree, ringbufferAddr); MpegContext *ctx = getMpegCtx(ringbuffer.mpeg); int result = std::min(ringbuffer.packetsFree, ctx->mediaengine->getRemainSize() / 2048); return ringbuffer.packetsFree; }
int sceMpegGetAtracAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegGetAtracAu(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, streamId, auAddr, attrAddr); return -1; } SceMpegRingBuffer mpegRingbuffer; Memory::ReadStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); SceMpegAu sceAu; sceAu.read(auAddr); auto streamInfo = ctx->streamMap.find(streamId); if (streamInfo != ctx->streamMap.end() && streamInfo->second.needsReset) { sceAu.pts = 0; streamInfo->second.needsReset = false; } // The audio can end earlier than the video does. if (mpegRingbuffer.packetsFree == mpegRingbuffer.packets || (ctx->mediaengine->IsAudioEnd() && !ctx->mediaengine->IsVideoEnd())) { DEBUG_LOG(HLE, "PSP_ERROR_MPEG_NO_DATA=sceMpegGetAtracAu(%08x, %08x, %08x, %08x)", mpeg, streamId, auAddr, attrAddr); // TODO: Does this really delay? return hleDelayResult(PSP_ERROR_MPEG_NO_DATA, "mpeg get atrac", mpegDecodeErrorDelayMs); } int result = 0; sceAu.pts = ctx->mediaengine->getAudioTimeStamp() + ctx->mpegFirstTimestamp; if (ctx->mediaengine->IsVideoEnd()) { INFO_LOG(HLE, "video end reach. pts: %i dts: %i", (int)sceAu.pts, (int)ctx->mediaengine->getLastTimeStamp()); mpegRingbuffer.packetsFree = mpegRingbuffer.packets; Memory::WriteStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); result = PSP_ERROR_MPEG_NO_DATA; } sceAu.write(auAddr); if (Memory::IsValidAddress(attrAddr)) { Memory::Write_U32(0, attrAddr); } DEBUG_LOG(HLE, "%x=sceMpegGetAtracAu(%08x, %08x, %08x, %08x)", result, mpeg, streamId, auAddr, attrAddr); // TODO: Not clear on exactly when this delays. return hleDelayResult(result, "mpeg get atrac", 100); }
int sceMpegDelete(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegDelete(%08x): bad mpeg handle", mpeg); return -1; } DEBUG_LOG(HLE, "sceMpegDelete(%08x)", mpeg); delete ctx; mpegMap.erase(Memory::Read_U32(mpeg)); return 0; }
// YCbCr -> RGB color space conversion u32 sceMpegAvcCsc(u32 mpeg, u32 sourceAddr, u32 rangeAddr, int frameWidth, u32 destAddr) { DEBUG_LOG(HLE, "sceMpegAvcCsc(%08x, %08x, %08x, %i, %08x)", mpeg, sourceAddr, rangeAddr, frameWidth, destAddr); MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) return -1; if ((!Memory::IsValidAddress(rangeAddr)) || (!Memory::IsValidAddress(destAddr))) return -1; int x = Memory::Read_U32(rangeAddr); int y = Memory::Read_U32(rangeAddr + 4); int width = Memory::Read_U32(rangeAddr + 8); int height = Memory::Read_U32(rangeAddr + 12); int destSize = ctx->mediaengine->writeVideoImageWithRange(Memory::GetPointer(destAddr), frameWidth, ctx->videoPixelMode, x, y, width, height); gpu->InvalidateCache(destAddr, destSize, GPU_INVALIDATE_SAFE); return 0; }
u32 sceMpegChangeGetAuMode(u32 mpeg, int streamUid, int mode) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegChangeGetAuMode(%08x, %i, %i): bad mpeg handle", mpeg, streamUid, mode); return -1; } // NOTE: Where is the info supposed to come from? StreamInfo info = {0}; info.sid = streamUid; if (info.sid) { switch (info.type) { case MPEG_AVC_STREAM: if(mode == MPEG_AU_MODE_DECODE) { ctx->ignoreAvc = false; } else if (mode == MPEG_AU_MODE_SKIP) { ctx->ignoreAvc = true; } break; case MPEG_AUDIO_STREAM: case MPEG_ATRAC_STREAM: if(mode == MPEG_AU_MODE_DECODE) { ctx->ignoreAtrac = false; } else if (mode == MPEG_AU_MODE_SKIP) { ctx->ignoreAtrac = true; } break; case MPEG_PCM_STREAM: if(mode == MPEG_AU_MODE_DECODE) { ctx->ignorePcm = false; } else if (mode == MPEG_AU_MODE_SKIP) { ctx->ignorePcm = true; } break; default: ERROR_LOG(HLE, "UNIMPL sceMpegChangeGetAuMode(%08x, %i): unkown streamID", mpeg, streamUid); break; } } else { ERROR_LOG(HLE, "UNIMPL sceMpegChangeGetAuMode(%08x, %i): unkown streamID", mpeg, streamUid); } return 0; }
int sceMpegFreeAvcEsBuf(u32 mpeg, int esBuf) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegFreeAvcEsBuf(%08x, %i): bad mpeg handle", mpeg, esBuf); return -1; } DEBUG_LOG(HLE, "sceMpegFreeAvcEsBuf(%08x, %i)", mpeg, esBuf); if (esBuf == 0) { return ERROR_MPEG_INVALID_VALUE; } if (esBuf >= 1 && esBuf <= NUM_ES_BUFFERS) { // TODO: Check if it's already been free'd? ctx->esBuffers[esBuf - 1] = false; } return 0; }
int sceMpegMallocAvcEsBuf(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegMallocAvcEsBuf(%08x): bad mpeg handle", mpeg); return -1; } DEBUG_LOG(HLE, "sceMpegMallocAvcEsBuf(%08x)", mpeg); // Doesn't actually malloc, just keeps track of a couple of flags for (int i = 0; i < NUM_ES_BUFFERS; i++) { if (!ctx->esBuffers[i]) { ctx->esBuffers[i] = true; return i + 1; } } // No es buffer return 0; }
int sceMpegRingbufferAvailableSize(u32 ringbufferAddr) { PSPPointer<SceMpegRingBuffer> ringbuffer; ringbuffer = ringbufferAddr; if (!ringbuffer.IsValid()) { ERROR_LOG(HLE, "sceMpegRingbufferAvailableSize(%08x) - bad address", ringbufferAddr); return -1; } MpegContext *ctx = getMpegCtx(ringbuffer->mpeg); if (!ctx) { ERROR_LOG(HLE, "sceMpegRingbufferAvailableSize(%08x) - bad mpeg", ringbufferAddr); return -1; } hleEatCycles(2020); DEBUG_LOG(HLE, "%i=sceMpegRingbufferAvailableSize(%08x)", ringbuffer->packetsFree, ringbufferAddr); return ringbuffer->packetsFree; }
int sceMpegRegistStream(u32 mpeg, u32 streamType, u32 streamNum) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegRegistStream(%08x, %i, %i): bad mpeg handle", mpeg, streamType, streamNum); return -1; } INFO_LOG(HLE, "sceMpegRegistStream(%08x, %i, %i)", mpeg, streamType, streamNum); switch (streamType) { case MPEG_AVC_STREAM: ctx->avcRegistered = true; ctx->mediaengine->setVideoStream(streamNum); break; case MPEG_AUDIO_STREAM: case MPEG_ATRAC_STREAM: ctx->atracRegistered = true; ctx->mediaengine->setAudioStream(streamNum); break; case MPEG_PCM_STREAM: ctx->pcmRegistered = true; break; case MPEG_DATA_STREAM: ctx->dataRegistered = true; break; default : DEBUG_LOG(HLE, "sceMpegRegistStream(%i) : unknown stream type", streamType); break; } // ... u32 sid = streamIdGen++; StreamInfo info; info.type = streamType; info.num = streamNum; info.needsReset = true; ctx->streamMap[sid] = info; return sid; }
void PostPutAction::run(MipsCall &call) { SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ringAddr_, &ringbuffer); MpegContext *ctx = getMpegCtx(ringbuffer.mpeg); int packetsAdded = currentMIPS->r[2]; if (packetsAdded > 0) { if (ctx) ctx->mediaengine->feedPacketData(ringbuffer.data, packetsAdded * ringbuffer.packetSize); if (packetsAdded > ringbuffer.packetsFree) { WARN_LOG(HLE, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAdded, ringbuffer.packetsFree); packetsAdded = ringbuffer.packetsFree; } ringbuffer.packetsRead += packetsAdded; ringbuffer.packetsWritten += packetsAdded; ringbuffer.packetsFree -= packetsAdded; } Memory::WriteStruct(ringAddr_, &ringbuffer); call.setReturnValue(packetsAdded); }
u32 sceMpegFlushAllStream(u32 mpeg) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegFlushAllStream(%08x): bad mpeg handle", mpeg); return -1; } WARN_LOG(HLE, "UNIMPL sceMpegFlushAllStream(%08x)", mpeg); ctx->isAnalyzed = false; if (Memory::IsValidAddress(ctx->mpegRingbufferAddr)) { auto ringbuffer = Memory::GetStruct<SceMpegRingBuffer>(ctx->mpegRingbufferAddr); ringbuffer->packetsFree = ringbuffer->packets; ringbuffer->packetsRead = 0; ringbuffer->packetsWritten = 0; } return 0; }
// Program signals that it has written data to the ringbuffer and gets a callback ? u32 sceMpegRingbufferPut(u32 ringbufferAddr, u32 numPackets, u32 available) { DEBUG_LOG(HLE, "sceMpegRingbufferPut(%08x, %i, %i)", ringbufferAddr, numPackets, available); numPackets = std::min(numPackets, available); if (numPackets <= 0) { ERROR_LOG(HLE, "sub-zero number of packets put"); return 0; } SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ringbufferAddr, &ringbuffer); MpegContext *ctx = getMpegCtx(ringbuffer.mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegRingbufferPut(%08x, %i, %i): bad mpeg handle %08x", ringbufferAddr, numPackets, available, ringbuffer.mpeg); return 0; } // Clamp to length of mpeg stream - this seems like a hack as we don't have access to the context here really int mpegStreamPackets = (ctx->mpegStreamSize + ringbuffer.packetSize - 1) / ringbuffer.packetSize; int remainingPackets = mpegStreamPackets - ringbuffer.packetsRead; if (remainingPackets < 0) { remainingPackets = 0; } numPackets = std::min(numPackets, (u32)remainingPackets); // Execute callback function as a direct MipsCall, no blocking here so no messing around with wait states etc if (ringbuffer.callback_addr) { PostPutAction *action = (PostPutAction *) __KernelCreateAction(actionPostPut); action->setRingAddr(ringbufferAddr); u32 args[3] = {(u32)ringbuffer.data, numPackets, (u32)ringbuffer.callback_args}; __KernelDirectMipsCall(ringbuffer.callback_addr, action, args, 3, false); } else { ERROR_LOG(HLE, "sceMpegRingbufferPut: callback_addr zero"); } return 0; }
u32 sceMpegUnRegistStream(u32 mpeg, int streamUid) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegUnRegistStream(%08x, %i): bad mpeg handle", mpeg, streamUid); return -1; } StreamInfo info = {0}; switch (info.type) { case MPEG_AVC_STREAM: ctx->avcRegistered = false; break; case MPEG_AUDIO_STREAM: case MPEG_ATRAC_STREAM: ctx->atracRegistered = false; break; case MPEG_PCM_STREAM: ctx->pcmRegistered = false; break; case MPEG_DATA_STREAM: ctx->dataRegistered = false; break; default : DEBUG_LOG(HLE, "sceMpegUnRegistStream(%i) : unknown streamID ", streamUid); break; } ctx->streamMap[streamUid] = info; info.type = -1; info.sid = -1 ; info.needsReset = true; ctx->isAnalyzed = false; return 0; }
int sceMpegAvcDecodeMode(u32 mpeg, u32 modeAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegAvcDecodeMode(%08x, %08x): bad mpeg handle", mpeg, modeAddr); return -1; } DEBUG_LOG(HLE, "sceMpegAvcDecodeMode(%08x, %08x)", mpeg, modeAddr); if (Memory::IsValidAddress(modeAddr)) { int mode = Memory::Read_U32(modeAddr); int pixelMode = Memory::Read_U32(modeAddr + 4); if (pixelMode >= TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650 && pixelMode <= TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) { ctx->videoPixelMode = pixelMode; } else { ERROR_LOG(HLE, "sceMpegAvcDecodeMode(%i, %i): unknown pixelMode ", mode, pixelMode); } } else { ERROR_LOG(HLE, "sceMpegAvcDecodeMode(%08x, %08x): invalid modeAddr", mpeg, modeAddr); return -1; } return 0; }
int sceMpegGetAvcAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegGetAvcAu(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, streamId, auAddr, attrAddr); return -1; } SceMpegRingBuffer mpegRingbuffer; Memory::ReadStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); SceMpegAu sceAu; sceAu.read(auAddr); if (mpegRingbuffer.packetsRead == 0 || mpegRingbuffer.packetsFree == mpegRingbuffer.packets) { DEBUG_LOG(HLE, "PSP_ERROR_MPEG_NO_DATA=sceMpegGetAvcAu(%08x, %08x, %08x, %08x)", mpeg, streamId, auAddr, attrAddr); // TODO: Does this really reschedule? return hleDelayResult(PSP_ERROR_MPEG_NO_DATA, "mpeg get avc", mpegDecodeErrorDelayMs); } auto streamInfo = ctx->streamMap.find(streamId); if (streamInfo == ctx->streamMap.end()) { ERROR_LOG(HLE, "sceMpegGetAvcAu - bad stream id %i", streamId); return -1; } if (streamInfo->second.needsReset) { sceAu.pts = 0; streamInfo->second.needsReset = false; } // Wait for audio if too much ahead if (ctx->atracRegistered && (sceAu.pts > sceAu.pts + getMaxAheadTimestamp(mpegRingbuffer))) { ERROR_LOG(HLE, "sceMpegGetAvcAu - video too much ahead"); // TODO: Does this really reschedule? return hleDelayResult(PSP_ERROR_MPEG_NO_DATA, "mpeg get avc", mpegDecodeErrorDelayMs); } int result = 0; // read the au struct from ram // TODO: For now, always checking, since readVideoAu() is stubbed. if (!ctx->mediaengine->readVideoAu(&sceAu) || true) { // Only return this after the video already ended. if (ctx->endOfVideoReached) { if (mpegRingbuffer.packetsFree < mpegRingbuffer.packets) { mpegRingbuffer.packetsFree = mpegRingbuffer.packets; Memory::WriteStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); } result = PSP_ERROR_MPEG_NO_DATA; } if (ctx->mpegLastTimestamp <= 0 || sceAu.pts >= ctx->mpegLastTimestamp) { NOTICE_LOG(HLE, "End of video reached"); ctx->endOfVideoReached = true; } else { ctx->endOfAudioReached = false; } } // The avcau struct may have been modified by mediaengine, write it back. sceAu.write(auAddr); if (Memory::IsValidAddress(attrAddr)) { Memory::Write_U32(1, attrAddr); } DEBUG_LOG(HLE, "%x=sceMpegGetAvcAu(%08x, %08x, %08x, %08x)", result, mpeg, streamId, auAddr, attrAddr); // TODO: sceMpegGetAvcAu seems to modify esSize, and delay when it's > 1000 or something. // There's definitely more to it, but ultimately it seems games should expect it to delay randomly. return hleDelayResult(result, "mpeg get avc", 100); }
int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, auAddr, bufferAddr, initAddr); return 0; } if (!Memory::IsValidAddress(auAddr) || !Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(initAddr)) { ERROR_LOG(HLE, "sceMpegAvcDecodeYCbCr: bad addresses"); return 0; } SceMpegAu avcAu; avcAu.read(auAddr); SceMpegRingBuffer ringbuffer = {0}; if (Memory::IsValidAddress(ctx->mpegRingbufferAddr)) { Memory::ReadStruct(ctx->mpegRingbufferAddr, &ringbuffer); } else { ERROR_LOG(HLE, "Bogus mpegringbufferaddr"); return -1; } if (ringbuffer.packetsRead == 0 || ctx->mediaengine->IsVideoEnd()) { WARN_LOG(HLE, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x): mpeg buffer empty", mpeg, auAddr, bufferAddr, initAddr); return hleDelayResult(MPEG_AVC_DECODE_ERROR_FATAL, "mpeg buffer empty", avcEmptyDelayMs); } u32 buffer = Memory::Read_U32(bufferAddr); u32 init = Memory::Read_U32(initAddr); DEBUG_LOG(HLE, "*buffer = %08x, *init = %08x", buffer, init); if (ctx->mediaengine->stepVideo(ctx->videoPixelMode)) { // Don't draw here, we'll draw in the Csc func. ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; }else { ctx->avc.avcFrameStatus = 0; } ringbuffer.packetsFree = ctx->mediaengine->getRemainSize() / 2048; avcAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp; ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; // Flush structs back to memory avcAu.write(auAddr); Memory::WriteStruct(ctx->mpegRingbufferAddr, &ringbuffer); Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); // 1 = showing, 0 = not showing DEBUG_LOG(HLE, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x)", mpeg, auAddr, bufferAddr, initAddr); if (ctx->videoFrameCount <= 1) return hleDelayResult(0, "mpeg decode", avcFirstDelayMs); else return hleDelayResult(0, "mpeg decode", avcDecodeDelayMs); //hleEatMicro(3300); //return hleDelayResult(0, "mpeg decode", 200); }
u32 sceMpegAvcDecode(u32 mpeg, u32 auAddr, u32 frameWidth, u32 bufferAddr, u32 initAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegAvcDecode(%08x, %08x, %d, %08x, %08x): bad mpeg handle", mpeg, auAddr, frameWidth, bufferAddr, initAddr); return 0; } if (!Memory::IsValidAddress(auAddr) || !Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(initAddr)) { ERROR_LOG(HLE, "sceMpegAvcDecode: bad addresses"); return 0; } if (frameWidth == 0) { // wtf, go sudoku passes in 0xccccccccc if (!ctx->defaultFrameWidth) { frameWidth = ctx->avc.avcDetailFrameWidth; } else { frameWidth = ctx->defaultFrameWidth; } } SceMpegAu avcAu; avcAu.read(auAddr); SceMpegRingBuffer ringbuffer = {0}; if (Memory::IsValidAddress(ctx->mpegRingbufferAddr)) { Memory::ReadStruct(ctx->mpegRingbufferAddr, &ringbuffer); } else { ERROR_LOG(HLE, "Bogus mpegringbufferaddr"); return -1; } if (ringbuffer.packetsRead == 0 || ctx->mediaengine->IsVideoEnd()) { WARN_LOG(HLE, "sceMpegAvcDecode(%08x, %08x, %d, %08x, %08x): mpeg buffer empty", mpeg, auAddr, frameWidth, bufferAddr, initAddr); return hleDelayResult(MPEG_AVC_DECODE_ERROR_FATAL, "mpeg buffer empty", avcEmptyDelayMs); } u32 buffer = Memory::Read_U32(bufferAddr); u32 init = Memory::Read_U32(initAddr); DEBUG_LOG(HLE, "*buffer = %08x, *init = %08x", buffer, init); if (ctx->mediaengine->stepVideo(ctx->videoPixelMode)) { int bufferSize = ctx->mediaengine->writeVideoImage(Memory::GetPointer(buffer), frameWidth, ctx->videoPixelMode); gpu->InvalidateCache(buffer, bufferSize, GPU_INVALIDATE_SAFE); ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; } else { ctx->avc.avcFrameStatus = 0; } ringbuffer.packetsFree = ctx->mediaengine->getRemainSize() / 2048; avcAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp; ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; // Flush structs back to memory avcAu.write(auAddr); Memory::WriteStruct(ctx->mpegRingbufferAddr, &ringbuffer); Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); // 1 = showing, 0 = not showing DEBUG_LOG(HLE, "sceMpegAvcDecode(%08x, %08x, %i, %08x, %08x)", mpeg, auAddr, frameWidth, bufferAddr, initAddr); if (ctx->videoFrameCount <= 1) return hleDelayResult(0, "mpeg decode", avcFirstDelayMs); else return hleDelayResult(0, "mpeg decode", avcDecodeDelayMs); //hleEatMicro(3300); //return hleDelayResult(0, "mpeg decode", 200); }
u32 sceMpegAvcDecode(u32 mpeg, u32 auAddr, u32 frameWidth, u32 bufferAddr, u32 initAddr) { if (!g_Config.bUseMediaEngine){ WARN_LOG(HLE, "Media Engine disabled"); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegAvcDecode(%08x, %08x, %d, %08x, %08x): bad mpeg handle", mpeg, auAddr, frameWidth, bufferAddr, initAddr); return 0; } if (!Memory::IsValidAddress(auAddr) || !Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(initAddr)) { ERROR_LOG(HLE, "sceMpegAvcDecode: bad addresses"); return 0; } if (frameWidth == 0) { // wtf, go sudoku passes in 0xccccccccc if (!ctx->defaultFrameWidth) { frameWidth = ctx->avc.avcDetailFrameWidth; } else { frameWidth = ctx->defaultFrameWidth; } } SceMpegAu avcAu; avcAu.read(auAddr); SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ctx->mpegRingbufferAddr, &ringbuffer); if (ringbuffer.packetsRead == 0) { // empty! return hleDelayResult(MPEG_AVC_DECODE_ERROR_FATAL, "mpeg buffer empty", avcEmptyDelayMs); } u32 buffer = Memory::Read_U32(bufferAddr); u32 init = Memory::Read_U32(initAddr); DEBUG_LOG(HLE, "*buffer = %08x, *init = %08x", buffer, init); const int width = std::min((int)frameWidth, 480); const int height = ctx->avc.avcDetailFrameHeight; int packetsInRingBuffer = ringbuffer.packets - ringbuffer.packetsFree; int processedPackets = ringbuffer.packetsRead - packetsInRingBuffer; int processedSize = processedPackets * ringbuffer.packetSize; int packetsConsumed = 3; if (ctx->mpegStreamSize > 0 && ctx->mpegLastTimestamp > 0) { // Try a better approximation of the packets consumed based on the timestamp int processedSizeBasedOnTimestamp = (int) ((((float) avcAu.pts) / ctx->mpegLastTimestamp) * ctx->mpegStreamSize); if (processedSizeBasedOnTimestamp < processedSize) { packetsConsumed = 0; } else { packetsConsumed = (processedSizeBasedOnTimestamp - processedSize) / ringbuffer.packetSize; if (packetsConsumed > 10) { packetsConsumed = 10; } } DEBUG_LOG(HLE, "sceMpegAvcDecode consumed %d %d/%d %d", processedSizeBasedOnTimestamp, processedSize, ctx->mpegStreamSize, packetsConsumed); } if (ctx->mediaengine->stepVideo()) { ctx->mediaengine->writeVideoImage(buffer, frameWidth, ctx->videoPixelMode); packetsConsumed += ctx->mediaengine->readLength() / ringbuffer.packetSize; // The MediaEngine is already consuming all the remaining // packets when approaching the end of the video. The PSP // is only consuming the last packet when reaching the end, // not before. // Consuming all the remaining packets? if (ringbuffer.packetsFree + packetsConsumed >= ringbuffer.packets) { // Having not yet reached the last timestamp? if (ctx->mpegLastTimestamp > 0 && avcAu.pts < ctx->mpegLastTimestamp) { // Do not yet consume all the remaining packets, leave 2 packets packetsConsumed = ringbuffer.packets - ringbuffer.packetsFree - 2; } } ctx->mediaengine->setReadLength(ctx->mediaengine->readLength() - packetsConsumed * ringbuffer.packetSize); } else { // Consume all remaining packets packetsConsumed = ringbuffer.packets - ringbuffer.packetsFree; } ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; // Update the ringbuffer with the consumed packets if (ringbuffer.packetsFree < ringbuffer.packets && packetsConsumed > 0) { ringbuffer.packetsFree = std::min(ringbuffer.packets, ringbuffer.packetsFree + packetsConsumed); DEBUG_LOG(HLE, "sceMpegAvcDecode consumed %d packets, remaining %d packets", packetsConsumed, ringbuffer.packets - ringbuffer.packetsFree); } ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; // Flush structs back to memory avcAu.write(auAddr); Memory::WriteStruct(ctx->mpegRingbufferAddr, &ringbuffer); Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); // 1 = showing, 0 = not showing DEBUG_LOG(HLE, "sceMpegAvcDecode(%08x, %08x, %i, %08x, %08x)", mpeg, auAddr, frameWidth, bufferAddr, initAddr); if (ctx->videoFrameCount <= 1) return hleDelayResult(0, "mpeg decode", avcFirstDelayMs); else return hleDelayResult(0, "mpeg decode", avcDecodeDelayMs); }
int sceMpegGetAvcAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) { MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegGetAvcAu(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, streamId, auAddr, attrAddr); return -1; } SceMpegRingBuffer mpegRingbuffer; Memory::ReadStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); SceMpegAu sceAu; sceAu.read(auAddr); if (mpegRingbuffer.packetsRead == 0 || mpegRingbuffer.packetsFree == mpegRingbuffer.packets) { DEBUG_LOG(HLE, "PSP_ERROR_MPEG_NO_DATA=sceMpegGetAvcAu(%08x, %08x, %08x, %08x)", mpeg, streamId, auAddr, attrAddr); sceAu.pts = -1; sceAu.dts = -1; sceAu.write(auAddr); // TODO: Does this really reschedule? return hleDelayResult(PSP_ERROR_MPEG_NO_DATA, "mpeg get avc", mpegDecodeErrorDelayMs); } auto streamInfo = ctx->streamMap.find(streamId); if (streamInfo == ctx->streamMap.end()) { ERROR_LOG(HLE, "sceMpegGetAvcAu - bad stream id %i", streamId); return -1; } if (streamInfo->second.needsReset) { sceAu.pts = 0; streamInfo->second.needsReset = false; } /*// Wait for audio if too much ahead if (ctx->atracRegistered && (ctx->mediaengine->getVideoTimeStamp() > ctx->mediaengine->getAudioTimeStamp() + getMaxAheadTimestamp(mpegRingbuffer))) { ERROR_LOG(HLE, "sceMpegGetAvcAu - video too much ahead"); // TODO: Does this really reschedule? return hleDelayResult(PSP_ERROR_MPEG_NO_DATA, "mpeg get avc", mpegDecodeErrorDelayMs); }*/ int result = 0; sceAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp; sceAu.dts = sceAu.pts - videoTimestampStep; if (ctx->mediaengine->IsVideoEnd()) { INFO_LOG(HLE, "video end reach. pts: %i dts: %i", (int)sceAu.pts, (int)ctx->mediaengine->getLastTimeStamp()); mpegRingbuffer.packetsFree = mpegRingbuffer.packets; Memory::WriteStruct(ctx->mpegRingbufferAddr, &mpegRingbuffer); result = PSP_ERROR_MPEG_NO_DATA; } // The avcau struct may have been modified by mediaengine, write it back. sceAu.write(auAddr); if (Memory::IsValidAddress(attrAddr)) { Memory::Write_U32(1, attrAddr); } DEBUG_LOG(HLE, "%x=sceMpegGetAvcAu(%08x, %08x, %08x, %08x)", result, mpeg, streamId, auAddr, attrAddr); // TODO: sceMpegGetAvcAu seems to modify esSize, and delay when it's > 1000 or something. // There's definitely more to it, but ultimately it seems games should expect it to delay randomly. return hleDelayResult(result, "mpeg get avc", 100); }
int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initAddr) { if (!g_Config.bUseMediaEngine){ WARN_LOG(HLE, "Media Engine disabled"); return -1; } MpegContext *ctx = getMpegCtx(mpeg); if (!ctx) { WARN_LOG(HLE, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x): bad mpeg handle", mpeg, auAddr, bufferAddr, initAddr); return 0; } if (!Memory::IsValidAddress(auAddr) || !Memory::IsValidAddress(bufferAddr) || !Memory::IsValidAddress(initAddr)) { ERROR_LOG(HLE, "sceMpegAvcDecodeYCbCr: bad addresses"); return 0; } SceMpegAu avcAu; avcAu.read(auAddr); SceMpegRingBuffer ringbuffer; Memory::ReadStruct(ctx->mpegRingbufferAddr, &ringbuffer); if (ringbuffer.packetsRead == 0) { // empty! return hleDelayResult(MPEG_AVC_DECODE_ERROR_FATAL, "mpeg buffer empty", avcEmptyDelayMs); } u32 buffer = Memory::Read_U32(bufferAddr); u32 init = Memory::Read_U32(initAddr); DEBUG_LOG(HLE, "*buffer = %08x, *init = %08x", buffer, init); int packetsInRingBuffer = ringbuffer.packets - ringbuffer.packetsFree; int processedPackets = ringbuffer.packetsRead - packetsInRingBuffer; int processedSize = processedPackets * ringbuffer.packetSize; int packetsConsumed = 3; if (ctx->mpegStreamSize > 0 && ctx->mpegLastTimestamp > 0) { // Try a better approximation of the packets consumed based on the timestamp int processedSizeBasedOnTimestamp = (int) ((((float) avcAu.pts) / ctx->mpegLastTimestamp) * ctx->mpegStreamSize); if (processedSizeBasedOnTimestamp < processedSize) { packetsConsumed = 0; } else { packetsConsumed = (processedSizeBasedOnTimestamp - processedSize) / ringbuffer.packetSize; if (packetsConsumed > 10) { packetsConsumed = 10; } } DEBUG_LOG(HLE, "sceMpegAvcDecodeYCbCr consumed %d %d/%d %d", processedSizeBasedOnTimestamp, processedSize, ctx->mpegStreamSize, packetsConsumed); } if (ctx->mediaengine->stepVideo()) { // TODO: Write it somewhere or buffer it or something? packetsConsumed += ctx->mediaengine->readLength() / ringbuffer.packetSize; // Consuming all the remaining packets? if (ringbuffer.packetsFree + packetsConsumed >= ringbuffer.packets) { // Having not yet reached the last timestamp? if (ctx->mpegLastTimestamp > 0 && avcAu.pts < ctx->mpegLastTimestamp) { // Do not yet consume all the remaining packets, leave 2 packets packetsConsumed = ringbuffer.packets - ringbuffer.packetsFree - 2; } } ctx->mediaengine->setReadLength(ctx->mediaengine->readLength() - packetsConsumed * ringbuffer.packetSize); } else { // Consume all remaining packets packetsConsumed = ringbuffer.packets - ringbuffer.packetsFree; } ctx->avc.avcFrameStatus = 1; ctx->videoFrameCount++; // Update the ringbuffer with the consumed packets if (ringbuffer.packetsFree < ringbuffer.packets && packetsConsumed > 0) { ringbuffer.packetsFree = std::min(ringbuffer.packets, ringbuffer.packetsFree + packetsConsumed); DEBUG_LOG(HLE, "sceMpegAvcDecodeYCbCr consumed %d packets, remaining %d packets", packetsConsumed, ringbuffer.packets - ringbuffer.packetsFree); } ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; // Flush structs back to memory avcAu.write(auAddr); Memory::WriteStruct(ctx->mpegRingbufferAddr, &ringbuffer); Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); // 1 = showing, 0 = not showing DEBUG_LOG(HLE, "UNIMPL sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x)", mpeg, auAddr, bufferAddr, initAddr); if (ctx->videoFrameCount <= 1) return hleDelayResult(0, "mpeg decode", avcFirstDelayMs); else return hleDelayResult(0, "mpeg decode", avcDecodeDelayMs); }