void ElementaryStream::push(DemuxerStream& stream, u32 sz, PesHeader& pes) { std::lock_guard<std::mutex> lock(m_mutex); if (is_full()) { cellDmux->Error("es::push(): buffer is full"); Emu.Pause(); return; } u32 data_addr = put + 128 + size; size += sz; memcpy(vm::get_ptr<void>(data_addr), vm::get_ptr<void>(stream.addr), sz); stream.skip(sz); auto info = vm::ptr<CellDmuxAuInfoEx>::make(put); info->auAddr = put + 128; info->auSize = size; if (pes.new_au) { info->dts.lower = (u32)pes.dts; info->dts.upper = (u32)(pes.dts >> 32); info->pts.lower = (u32)pes.pts; info->pts.upper = (u32)(pes.pts >> 32); info->isRap = false; // TODO: set valid value info->reserved = 0; info->userData = stream.userdata; }
PesHeader::PesHeader(DemuxerStream& stream) : pts(0xffffffffffffffffull) , dts(0xffffffffffffffffull) , size(0) , new_au(false) { u16 header; stream.get(header); stream.get(size); if (size) { u8 empty = 0; u8 v; while (true) { stream.get(v); if (v != 0xFF) break; // skip padding bytes empty++; if (empty == size) return; }; if ((v & 0xF0) == 0x20 && (size - empty) >= 5) // pts only { new_au = true; pts = stream.get_ts(v); stream.skip(size - empty - 5); } else { new_au = true; if ((v & 0xF0) != 0x30 || (size - empty) < 10) { cellDmux->Error("PesHeader(): pts not found"); Emu.Pause(); } pts = stream.get_ts(v); stream.get(v); if ((v & 0xF0) != 0x10) { cellDmux->Error("PesHeader(): dts not found"); Emu.Pause(); } dts = stream.get_ts(v); stream.skip(size - empty - 10); } } }
void ElementaryStream::push(DemuxerStream& stream, u32 size) { auto const old_size = raw_data.size(); raw_data.resize(old_size + size); memcpy(raw_data.data() + old_size, vm::get_ptr<void>(stream.addr), size); // append bytes stream.skip(size); }
void dmuxOpen(u32 dmux_id) // TODO: call from the constructor { const auto sptr = Emu.GetIdManager().get<Demuxer>(dmux_id); Demuxer& dmux = *sptr; dmux.id = dmux_id; dmux.dmuxCb = Emu.GetIdManager().make_ptr<PPUThread>(fmt::format("Demuxer[0x%x] Thread", dmux_id)); dmux.dmuxCb->prio = 1001; dmux.dmuxCb->stack_size = 0x10000; dmux.dmuxCb->custom_task = [sptr](PPUThread& CPU) { Demuxer& dmux = *sptr; DemuxerTask task; DemuxerStream stream = {}; ElementaryStream* esALL[96]; memset(esALL, 0, sizeof(esALL)); ElementaryStream** esAVC = &esALL[0]; // AVC (max 16 minus M2V count) ElementaryStream** esM2V = &esALL[16]; // M2V (max 16 minus AVC count) ElementaryStream** esDATA = &esALL[32]; // user data (max 16) ElementaryStream** esATX = &esALL[48]; // ATRAC3+ (max 16) ElementaryStream** esAC3 = &esALL[64]; // AC3 (max 16) ElementaryStream** esPCM = &esALL[80]; // LPCM (max 16) u32 cb_add = 0; while (true) { if (Emu.IsStopped() || dmux.is_closed) { break; } if (!dmux.job.try_peek(task) && dmux.is_running && stream.addr) { // default task (demuxing) (if there is no other work) be_t<u32> code; be_t<u16> len; if (!stream.peek(code)) { // demuxing finished dmux.is_running = false; // callback auto dmuxMsg = vm::ptr<CellDmuxMsg>::make(dmux.memAddr + (cb_add ^= 16)); dmuxMsg->msgType = CELL_DMUX_MSG_TYPE_DEMUX_DONE; dmuxMsg->supplementalInfo = stream.userdata; dmux.cbFunc(CPU, dmux.id, dmuxMsg, dmux.cbArg); dmux.is_working = false; stream = {}; continue; } switch (code.value()) { case PACK_START_CODE: { if (!stream.check(14)) { throw EXCEPTION("End of stream (PACK_START_CODE)"); } stream.skip(14); break; } case SYSTEM_HEADER_START_CODE: { if (!stream.check(18)) { throw EXCEPTION("End of stream (SYSTEM_HEADER_START_CODE)"); } stream.skip(18); break; } case PADDING_STREAM: { if (!stream.check(6)) { throw EXCEPTION("End of stream (PADDING_STREAM)"); } stream.skip(4); stream.get(len); if (!stream.check(len)) { throw EXCEPTION("End of stream (PADDING_STREAM, len=%d)", len); } stream.skip(len); break; } case PRIVATE_STREAM_2: { if (!stream.check(6)) { throw EXCEPTION("End of stream (PRIVATE_STREAM_2)"); } stream.skip(4); stream.get(len); cellDmux.Notice("PRIVATE_STREAM_2 (%d)", len); if (!stream.check(len)) { throw EXCEPTION("End of stream (PRIVATE_STREAM_2, len=%d)", len); } stream.skip(len); break; } case PRIVATE_STREAM_1: { // audio and user data stream DemuxerStream backup = stream; if (!stream.check(6)) { throw EXCEPTION("End of stream (PRIVATE_STREAM_1)"); } stream.skip(4); stream.get(len); if (!stream.check(len)) { throw EXCEPTION("End of stream (PRIVATE_STREAM_1, len=%d)", len); } const PesHeader pes(stream); if (!pes.is_ok) { throw EXCEPTION("PesHeader error (PRIVATE_STREAM_1, len=%d)", len); } if (len < pes.size + 4) { throw EXCEPTION("End of block (PRIVATE_STREAM_1, PesHeader + fid_minor, len=%d)", len); } len -= pes.size + 4; u8 fid_minor; if (!stream.get(fid_minor)) { throw EXCEPTION("End of stream (PRIVATE_STREAM1, fid_minor)"); } const u32 ch = fid_minor % 16; if ((fid_minor & -0x10) == 0 && esATX[ch]) { ElementaryStream& es = *esATX[ch]; if (es.raw_data.size() > 1024 * 1024) { stream = backup; std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack continue; } if (len < 3 || !stream.check(3)) { throw EXCEPTION("End of block (ATX, unknown header, len=%d)", len); } len -= 3; stream.skip(3); if (pes.has_ts) { es.last_dts = pes.dts; es.last_pts = pes.pts; } es.push(stream, len); while (true) { auto const size = es.raw_data.size() - es.raw_pos; // size of available new data auto const data = es.raw_data.data() + es.raw_pos; // pointer to available data if (size < 8) break; // skip if cannot read ATS header if (data[0] != 0x0f || data[1] != 0xd0) { throw EXCEPTION("ATX: 0x0fd0 header not found (ats=0x%llx)", *(be_t<u64>*)data); } u32 frame_size = ((((u32)data[2] & 0x3) << 8) | (u32)data[3]) * 8 + 8; if (size < frame_size + 8) break; // skip non-complete AU if (es.isfull(frame_size + 8)) break; // skip if cannot push AU es.push_au(frame_size + 8, es.last_dts, es.last_pts, stream.userdata, false /* TODO: set correct value */, 0); //cellDmux.Notice("ATX AU pushed (ats=0x%llx, frame_size=%d)", *(be_t<u64>*)data, frame_size); auto esMsg = vm::ptr<CellDmuxEsMsg>::make(dmux.memAddr + (cb_add ^= 16)); esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_AU_FOUND; esMsg->supplementalInfo = stream.userdata; es.cbFunc(CPU, dmux.id, es.id, esMsg, es.cbArg); } } else { cellDmux.Notice("PRIVATE_STREAM_1 (len=%d, fid_minor=0x%x)", len, fid_minor); stream.skip(len); } break; } case 0x1e0: case 0x1e1: case 0x1e2: case 0x1e3: case 0x1e4: case 0x1e5: case 0x1e6: case 0x1e7: case 0x1e8: case 0x1e9: case 0x1ea: case 0x1eb: case 0x1ec: case 0x1ed: case 0x1ee: case 0x1ef: { // video stream (AVC or M2V) DemuxerStream backup = stream; if (!stream.check(6)) { throw EXCEPTION("End of stream (video, code=0x%x)", code); } stream.skip(4); stream.get(len); if (!stream.check(len)) { throw EXCEPTION("End of stream (video, code=0x%x, len=%d)", code, len); } const PesHeader pes(stream); if (!pes.is_ok) { throw EXCEPTION("PesHeader error (video, code=0x%x, len=%d)", code, len); } if (len < pes.size + 3) { throw EXCEPTION("End of block (video, code=0x%x, PesHeader)", code); } len -= pes.size + 3; const u32 ch = code % 16; if (esAVC[ch]) { ElementaryStream& es = *esAVC[ch]; const u32 old_size = (u32)es.raw_data.size(); if (es.isfull(old_size)) { stream = backup; std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack continue; } if ((pes.has_ts && old_size) || old_size >= 0x69800) { // push AU if it becomes too big or the next packet contains PTS/DTS es.push_au(old_size, es.last_dts, es.last_pts, stream.userdata, false /* TODO: set correct value */, 0); // callback auto esMsg = vm::ptr<CellDmuxEsMsg>::make(dmux.memAddr + (cb_add ^= 16)); esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_AU_FOUND; esMsg->supplementalInfo = stream.userdata; es.cbFunc(CPU, dmux.id, es.id, esMsg, es.cbArg); } if (pes.has_ts) { // preserve dts/pts for next AU es.last_dts = pes.dts; es.last_pts = pes.pts; } // reconstruction of MPEG2-PS stream for vdec module const u32 size = len + pes.size + 9; stream = backup; es.push(stream, size); } else { cellDmux.Notice("Video stream (code=0x%x, len=%d)", code, len); stream.skip(len); } break; } default: { if ((code & PACKET_START_CODE_MASK) == PACKET_START_CODE_PREFIX) { throw EXCEPTION("Unknown code found (0x%x)", code); } // search stream.skip(1); } } continue; } // wait for task if no work if (!dmux.job.pop(task, &dmux.is_closed)) { break; // Emu is stopped } switch (task.type) { case dmuxSetStream: { if (task.stream.discontinuity) { cellDmux.Warning("dmuxSetStream (beginning)"); for (u32 i = 0; i < sizeof(esALL) / sizeof(esALL[0]); i++) { if (esALL[i]) { esALL[i]->reset(); } } } stream = task.stream; //LOG_NOTICE(HLE, "*** stream updated(addr=0x%x, size=0x%x, discont=%d, userdata=0x%llx)", //stream.addr, stream.size, stream.discontinuity, stream.userdata); break; } case dmuxResetStream: case dmuxResetStreamAndWaitDone: { // demuxing stopped if (dmux.is_running.exchange(false)) { // callback auto dmuxMsg = vm::ptr<CellDmuxMsg>::make(dmux.memAddr + (cb_add ^= 16)); dmuxMsg->msgType = CELL_DMUX_MSG_TYPE_DEMUX_DONE; dmuxMsg->supplementalInfo = stream.userdata; dmux.cbFunc(CPU, dmux.id, dmuxMsg, dmux.cbArg); stream = {}; dmux.is_working = false; } break; } case dmuxEnableEs: { ElementaryStream& es = *task.es.es_ptr; // TODO: uncomment when ready to use if ((es.fidMajor & -0x10) == 0xe0 && es.fidMinor == 0 && es.sup1 == 1 && !es.sup2) { esAVC[es.fidMajor % 16] = task.es.es_ptr; } //else if ((es.fidMajor & -0x10) == 0xe0 && es.fidMinor == 0 && !es.sup1 && !es.sup2) //{ // esM2V[es.fidMajor % 16] = task.es.es_ptr; //} else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0 && !es.sup1 && !es.sup2) { esATX[es.fidMinor % 16] = task.es.es_ptr; } //else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0x20 && !es.sup1 && !es.sup2) //{ // esDATA[es.fidMinor % 16] = task.es.es_ptr; //} //else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0x30 && !es.sup1 && !es.sup2) //{ // esAC3[es.fidMinor % 16] = task.es.es_ptr; //} //else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0x40 && !es.sup1 && !es.sup2) //{ // esPCM[es.fidMinor % 16] = task.es.es_ptr; //} else { throw EXCEPTION("dmuxEnableEs: unknown filter (0x%x, 0x%x, 0x%x, 0x%x)", es.fidMajor, es.fidMinor, es.sup1, es.sup2); } es.dmux = &dmux; break; } case dmuxDisableEs: { ElementaryStream& es = *task.es.es_ptr; if (es.dmux != &dmux) { throw EXCEPTION("dmuxDisableEs: invalid elementary stream"); } for (u32 i = 0; i < sizeof(esALL) / sizeof(esALL[0]); i++) { if (esALL[i] == &es) { esALL[i] = nullptr; } } es.dmux = nullptr; Emu.GetIdManager().remove<ElementaryStream>(task.es.es); break; } case dmuxFlushEs: { ElementaryStream& es = *task.es.es_ptr; const u32 old_size = (u32)es.raw_data.size(); if (old_size && (es.fidMajor & -0x10) == 0xe0) { // TODO (it's only for AVC, some ATX data may be lost) while (es.isfull(old_size)) { if (Emu.IsStopped() || dmux.is_closed) break; std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack } es.push_au(old_size, es.last_dts, es.last_pts, stream.userdata, false, 0); // callback auto esMsg = vm::ptr<CellDmuxEsMsg>::make(dmux.memAddr + (cb_add ^= 16)); esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_AU_FOUND; esMsg->supplementalInfo = stream.userdata; es.cbFunc(CPU, dmux.id, es.id, esMsg, es.cbArg); } if (es.raw_data.size()) { cellDmux.Error("dmuxFlushEs: 0x%x bytes lost (es_id=%d)", (u32)es.raw_data.size(), es.id); } // callback auto esMsg = vm::ptr<CellDmuxEsMsg>::make(dmux.memAddr + (cb_add ^= 16)); esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_FLUSH_DONE; esMsg->supplementalInfo = stream.userdata; es.cbFunc(CPU, dmux.id, es.id, esMsg, es.cbArg); break; } case dmuxResetEs: { task.es.es_ptr->reset(); break; } case dmuxClose: { break; } default: { throw EXCEPTION("Demuxer thread error: unknown task (0x%x)", task.type); } } } dmux.is_finished = true; }; dmux.dmuxCb->run(); dmux.dmuxCb->exec(); }
PesHeader::PesHeader(DemuxerStream& stream) : pts(CODEC_TS_INVALID) , dts(CODEC_TS_INVALID) , size(0) , has_ts(false) , is_ok(false) { u16 header; if (!stream.get(header)) { throw EXCEPTION("End of stream (header)"); } if (!stream.get(size)) { throw EXCEPTION("End of stream (size)"); } if (!stream.check(size)) { throw EXCEPTION("End of stream (size=%d)", size); } u8 pos = 0; while (pos++ < size) { u8 v; if (!stream.get(v)) { return; // should never occur } if (v == 0xff) // skip padding bytes { continue; } if ((v & 0xf0) == 0x20 && (size - pos) >= 4) // pts only { pos += 4; pts = stream.get_ts(v); has_ts = true; } else if ((v & 0xf0) == 0x30 && (size - pos) >= 9) // pts and dts { pos += 5; pts = stream.get_ts(v); stream.get(v); has_ts = true; if ((v & 0xf0) != 0x10) { cellDmux.Error("PesHeader(): dts not found (v=0x%x, size=%d, pos=%d)", v, size, pos - 1); stream.skip(size - pos); return; } pos += 4; dts = stream.get_ts(v); } else { cellDmux.Warning("PesHeader(): unknown code (v=0x%x, size=%d, pos=%d)", v, size, pos - 1); stream.skip(size - pos); pos = size; break; } } is_ok = true; }