From ec7c838e5a79f2f538a7dc8b776a4acdb4ae83fd Mon Sep 17 00:00:00 2001 From: vibecodekun <328927837+vibecodekun@users.noreply.github.com> Date: Fri, 25 Sep 2026 23:28:23 -0400 Subject: [PATCH] Emulate the Digital Video Cartridge (VMPEG) Add the 22ER9141 Digital Video Cartridge as fitted to the Mono-I, and as built into the CD-i 490: an MCD251 video decoder and a DSP56001 doing MPEG-1 audio in software, driven by the fmvdrv/madriv modules in the cartridge ROM. Titles that need it for full motion video, such as The Firm, Mutant Rampage and Monty Python's Invasion from the Planet Skyron, now play. The register file, interrupt behaviour, program stream demultiplexer and DMA handshake are ported from the CDi_MiSTer VMPEG core by Andre Zeps, which reverse-engineered the cartridge. CDi_MiSTer is GPL-3.0, so cdidvc.cpp/.h and mpeg1demux.cpp/.h carry that license and the README says so. The MPEG-1 video and Layer I/II audio decoders are new code under BSD-3-Clause; their tables follow pl_mpeg, whose MIT notice is reproduced in both. The cartridge is fitted to cdimono1 unconditionally, but its driver ROM, vmpega.rom, is optional. Without it the boot code's cartridge probe finds nothing and the machine behaves exactly as before, so existing BIOS sets keep working. With it, the cartridge decodes d00000-efffff: 1MB of extra system RAM, the register file, the ROM, and 512KB of MPEG working RAM that bus-errors until the driver unlocks it, so that the OS-9 memory crawler does not claim it. IRQ level 4 is shared between the CDIC and the cartridge. A flip-flop on the board gives the line to whichever device raised it first and routes the interrupt acknowledge to it; the driver now arbitrates the same way instead of wiring the CDIC straight to the CPU. The picture reaches the screen through the MCD212's External Video backdrop. The cartridge's display window keys the CD-i planes away where region flags select it, so a title that frames its video with a border drawn in a plane keeps the border. Outside the window, or with no cartridge, the backdrop is the CD-i's 16-level black rather than 0. The software list no longer filters out DVC titles. Co-Authored-By: Claude Opus 5.5 --- Makefile.common | 4 + README.md | 22 + src/mame/drivers/cdi.cpp | 196 +++- src/mame/includes/cdi.h | 32 + src/mame/machine/cdidvc.cpp | 1516 ++++++++++++++++++++++++ src/mame/machine/cdidvc.h | 401 +++++++ src/mame/machine/mpeg1audio.cpp | 872 ++++++++++++++ src/mame/machine/mpeg1audio.h | 177 +++ src/mame/machine/mpeg1demux.cpp | 297 +++++ src/mame/machine/mpeg1demux.h | 101 ++ src/mame/machine/mpeg1video.cpp | 1936 +++++++++++++++++++++++++++++++ src/mame/machine/mpeg1video.h | 327 ++++++ src/mame/video/mcd212.cpp | 68 +- src/mame/video/mcd212.h | 33 +- 14 files changed, 5966 insertions(+), 16 deletions(-) create mode 100644 src/mame/machine/cdidvc.cpp create mode 100644 src/mame/machine/cdidvc.h create mode 100644 src/mame/machine/mpeg1audio.cpp create mode 100644 src/mame/machine/mpeg1audio.h create mode 100644 src/mame/machine/mpeg1demux.cpp create mode 100644 src/mame/machine/mpeg1demux.h create mode 100644 src/mame/machine/mpeg1video.cpp create mode 100644 src/mame/machine/mpeg1video.h diff --git a/Makefile.common b/Makefile.common index 7ae74d54..189252b4 100644 --- a/Makefile.common +++ b/Makefile.common @@ -90,7 +90,11 @@ OBJECTS += $(MESS_OBJS) MAME_MESS_SRC_CXX := \ $(CORE_DIR)/src/mame/drivers/cdi.cpp \ $(CORE_DIR)/src/mame/machine/cdicdic.cpp \ + $(CORE_DIR)/src/mame/machine/cdidvc.cpp \ $(CORE_DIR)/src/mame/machine/cdislavehle.cpp \ + $(CORE_DIR)/src/mame/machine/mpeg1audio.cpp \ + $(CORE_DIR)/src/mame/machine/mpeg1demux.cpp \ + $(CORE_DIR)/src/mame/machine/mpeg1video.cpp \ $(CORE_DIR)/src/mame/video/mcd212.cpp MAME_MESS_OBJS := $(patsubst $(CORE_DIR)/%,$(CORE_OBJ_DIR)/%,$(MAME_MESS_SRC_CXX:.cpp=.o)) diff --git a/README.md b/README.md index de06660d..81f0c5c4 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,19 @@ Game files can be either CHD, ISO, or BIN/CUE. Game support/functionality is wh ======= BIOS file (cdimono1.zip) is required, and can go either in the same directory as your ```CHD|ISO|CUE``` files or in the ```retroarch_system_dir/same_cdi/bios/``` directory +*DIGITAL VIDEO CARTRIDGE* +======= +Titles that need the Digital Video Cartridge for MPEG-1 full motion video, +such as *The Firm*, *Mutant Rampage: Bodyslam* and *Monty Python's Invasion +from the Planet Skyron*, also need the cartridge's driver ROM. Add +`vmpega.rom` (262144 bytes, CRC32 `db264e8b`) to `cdimono1.zip`; the same +file is part of `cdi490a.zip`, since the CD-i 490 has the hardware built in. +Without it the core runs as a plain Mono-I with no cartridge fitted, exactly +as before. + +The cartridge is emulated from the reverse engineering done by the +[CDi_MiSTer](https://github.com/MiSTer-devel/CDi_MiSTer) project. + -------- # **Libretro notice** # @@ -36,6 +49,15 @@ including core files) are made available under the terms of the would encourage new contributors to make their contributions available under the terms of this license. +The Digital Video Cartridge emulation (`src/mame/machine/cdidvc.*` and +`src/mame/machine/mpeg1demux.*`) is a port of the +[CDi_MiSTer](https://github.com/MiSTer-devel/CDi_MiSTer) VMPEG core and is +therefore made available under the terms of the +[GNU General Public License, version 3](https://opensource.org/licenses/GPL-3.0) +(GPL-3.0), like the project it was ported from. Every other license in the +tree is compatible with it, and binaries of this core that include those files +are distributed under GPL-3.0. + Please note that MAME is a registered trademark of Gregory Ember, and permission is required to use the "MAME" name, logo, or wordmark. diff --git a/src/mame/drivers/cdi.cpp b/src/mame/drivers/cdi.cpp index 85d6b117..a703c165 100644 --- a/src/mame/drivers/cdi.cpp +++ b/src/mame/drivers/cdi.cpp @@ -99,6 +99,35 @@ void cdi_state::cdimono1_mem(address_map &map) map(0xe80000, 0xefffff).ram(); // DVC RAM block 2 } +// As above, but with a Digital Video Cartridge fitted. The cartridge decodes +// its own quarter of the upper address space: +// +// d00000-dfffff 1MB of extra system RAM contributed by the cartridge +// e00000-e3ffff VMPEG register file (address bits 15..1 only, so it mirrors) +// e40000-e7ffff 128KB driver ROM, mirrored twice +// e80000-efffff 512KB MPEG working RAM, bus-errors until the driver unlocks it +void cdi_state::cdimono1_dvc_mem(address_map &map) +{ + map(0x000000, 0xffffff).rw(FUNC(cdi_state::bus_error_r), FUNC(cdi_state::bus_error_w)); + map(0x000000, 0x07ffff).rw(FUNC(cdi_state::plane_r<0>), FUNC(cdi_state::plane_w<0>)).share("plane0"); + map(0x200000, 0x27ffff).rw(FUNC(cdi_state::plane_r<1>), FUNC(cdi_state::plane_w<1>)).share("plane1"); + map(0x300000, 0x303bff).rw(m_cdic, FUNC(cdicdic_device::ram_r), FUNC(cdicdic_device::ram_w)); +#if ENABLE_UART_PRINTING + map(0x301400, 0x301403).r(m_maincpu, FUNC(scc68070_device::uart_loopback_enable)); +#endif + map(0x303c00, 0x303fff).rw(m_cdic, FUNC(cdicdic_device::regs_r), FUNC(cdicdic_device::regs_w)); + map(0x310000, 0x317fff).rw(m_slave_hle, FUNC(cdislave_hle_device::slave_r), FUNC(cdislave_hle_device::slave_w)); + map(0x318000, 0x31ffff).noprw(); + map(0x320000, 0x323fff).rw("mk48t08", FUNC(timekeeper_device::read), FUNC(timekeeper_device::write)).umask16(0xff00); + map(0x400000, 0x47ffff).r(FUNC(cdi_state::main_rom_r)); + map(0x4fffe0, 0x4fffff).m(m_mcd212, FUNC(mcd212_device::map)); + map(0x500000, 0x57ffff).ram(); + map(0xd00000, 0xdfffff).ram(); + map(0xe00000, 0xe3ffff).rw(m_dvc, FUNC(cdidvc_device::regs_r), FUNC(cdidvc_device::regs_w)); + map(0xe40000, 0xe7ffff).r(FUNC(cdi_state::dvc_rom_r)); + map(0xe80000, 0xefffff).rw(FUNC(cdi_state::dvc_mpeg_ram_r), FUNC(cdi_state::dvc_mpeg_ram_w)); +} + void cdi_state::cdimono2_mem(address_map &map) { map(0x000000, 0x07ffff).ram().share("plane0"); @@ -209,6 +238,129 @@ void cdi_state::dvc_w(offs_t offset, uint16_t data, uint16_t mem_mask) { } +// The cartridge ROM is a 128KB device that appears twice across the 256KB +// window. It is stored in 68000 byte order, so assemble words explicitly +// rather than relying on host endianness. +uint16_t cdi_state::dvc_rom_r(offs_t offset) +{ + if (!m_dvc_rom.found()) + return 0; + + const offs_t addr = (offset & 0xffff) * 2; + const uint16_t data = (uint16_t(m_dvc_rom[addr]) << 8) | m_dvc_rom[addr + 1]; + + LOGMASKED(LOG_DVC, "%s: DVC ROM read %06x = %04x\n", machine().describe_context(), 0xe40000 + addr, data); + + return data; +} + +// The MPEG working RAM is invisible until the driver has unlocked it, so that +// the OS-9 memory crawler does not claim it as general-purpose system RAM. +uint16_t cdi_state::dvc_mpeg_ram_r(offs_t offset, uint16_t mem_mask) +{ + if (!m_dvc->mpeg_ram_enabled()) + return bus_error_r(offset + 0xe80000 / 2); + + return m_dvc->mpeg_ram_r(offset, mem_mask); +} + +void cdi_state::dvc_mpeg_ram_w(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + if (!m_dvc->mpeg_ram_enabled()) + { + bus_error_w(offset + 0xe80000 / 2, data); + return; + } + + m_dvc->mpeg_ram_w(offset, data, mem_mask); +} + +uint32_t cdi_state::dvc_video_r(int x, int y) +{ + // The cartridge places its picture in the CD-i's normal 384-pixel-wide + // display space, which the driver confirms by programming an active area + // of 384x280. The MCD212 renders 768 pixels across a line, so every + // cartridge pixel covers two of them. + if (!m_dvc.found() || !m_dvc->window_covers(x / 2, y)) + return 0; + + // Inside the window the cartridge owns the pixel even when it has no + // picture to show - between clips, or once playback has been stopped or + // skipped - and what it puts there is black. Reporting the window as + // unoccupied instead would hand the pixel back to the CD-i plane, and the + // artwork the title hides behind the window would show through. + // + // That black is the CD-i's black, not zero: the cartridge's picture is + // carried in the same 16-235 swing as everything else the MCD212 emits (see + // display_frame_rgb()), so an empty window has to sit at the same level as + // the surrounding planes or it reads as a darker rectangle. + uint32_t pixel = mcd212_device::CDI_BLACK; + if (!m_dvc->get_pixel(x / 2, y, pixel)) + pixel = mcd212_device::CDI_BLACK; + + return 0xff000000 | pixel; +} + +/************************************** +* IRQ level 4 arbitration * +**************************************/ + +void cdi_state::cdic_intreq_w(int state) +{ + m_cdic_intreq = bool(state); + update_irq4(); +} + +void cdi_state::dvc_intreq_w(int state) +{ + m_dvc_intreq = bool(state); + update_irq4(); +} + +void cdi_state::update_irq4() +{ + // Mirrors the 74ACT74 flip-flop on the board: whichever device grabs the + // line keeps it until it stops requesting. If both are idle and both + // request at once, the cartridge wins. + switch (m_irq4_owner) + { + case IRQ4_CDIC: + if (!m_cdic_intreq) + m_irq4_owner = IRQ4_IDLE; + break; + case IRQ4_DVC: + if (!m_dvc_intreq) + m_irq4_owner = IRQ4_IDLE; + break; + default: + break; + } + + if (m_irq4_owner == IRQ4_IDLE) + { + if (m_cdic_intreq) + m_irq4_owner = IRQ4_CDIC; + if (m_dvc_intreq) + m_irq4_owner = IRQ4_DVC; + } + + const bool line = (m_irq4_owner == IRQ4_CDIC && m_cdic_intreq) + || (m_irq4_owner == IRQ4_DVC && m_dvc_intreq); + + m_maincpu->in4_w(line ? 1 : 0); +} + +uint8_t cdi_state::iack4_r() +{ + if (m_irq4_owner == IRQ4_DVC && m_dvc.found()) + return m_dvc->intack_r(); + + if (m_cdic.found()) + return m_cdic->intack_r(); + + return 0; +} + /************************* * LCD screen * *************************/ @@ -278,7 +430,7 @@ void cdi_state::cdimono1_base(machine_config &config) { SCC68070(config, m_maincpu, CLOCK_A); m_maincpu->set_addrmap(AS_PROGRAM, &cdi_state::cdimono1_mem); - m_maincpu->iack4_callback().set(m_cdic, FUNC(cdicdic_device::intack_r)); + m_maincpu->iack4_callback().set(FUNC(cdi_state::iack4_r)); MCD212(config, m_mcd212, CLOCK_A, m_plane_ram[0], m_plane_ram[1]); m_mcd212->set_screen("screen"); @@ -304,7 +456,7 @@ void cdi_state::cdimono1_base(machine_config &config) // DSP input clock is 7.5264 MHz CDI_CDIC(config, m_cdic, 45.1584_MHz_XTAL / 2); m_cdic->set_clock2(45.1584_MHz_XTAL * 3 / 7); // generated by PLL circuit incorporating 19.3575 MHz XTAL - m_cdic->intreq_callback().set(m_maincpu, FUNC(scc68070_device::in4_w)); + m_cdic->intreq_callback().set(FUNC(cdi_state::cdic_intreq_w)); CDI_SLAVE_HLE(config, m_slave_hle, 0); m_slave_hle->int_callback().set(m_maincpu, FUNC(scc68070_device::in2_w)); @@ -413,12 +565,35 @@ void cdi_state::cdi910(machine_config &config) } // CD-i Mono-I, with CD-ROM image device (MESS) and Software List (MESS) +// +// The Digital Video Cartridge is fitted unconditionally, but its driver ROM is +// optional. Without the ROM the cartridge probe performed by the player's +// boot code finds nothing and the machine behaves exactly like a bare Mono-I, +// so existing BIOS sets keep working untouched. void cdi_state::cdimono1(machine_config &config) { cdimono1_base(config); + cdimono1_dvc(config); + CDROM(config, "cdrom").set_interface("cdi_cdrom"); - SOFTWARE_LIST(config, "cd_list").set_original("cdi").set_filter("!DVC"); + SOFTWARE_LIST(config, "cd_list").set_original("cdi"); +} + +// Digital Video Cartridge hookup, shared by the Mono-I and the CD-i 490 +void cdi_state::cdimono1_dvc(machine_config &config) +{ + m_maincpu->set_addrmap(AS_PROGRAM, &cdi_state::cdimono1_dvc_mem); + + CDI_DVC(config, m_dvc, 0); + // The cartridge needs the screen so that it can swap its picture over + // between fields rather than in the middle of one. + m_dvc->set_screen("screen"); + m_dvc->intreq_callback().set(FUNC(cdi_state::dvc_intreq_w)); + m_dvc->add_route(0, "lspeaker", 1.0); + m_dvc->add_route(1, "rspeaker", 1.0); + + m_mcd212->set_ext_video_callback(FUNC(cdi_state::dvc_video_r)); } /************************* @@ -440,6 +615,12 @@ ROM_START( cdimono1 ) ROM_REGION(0x2000, "slave", 0) ROM_LOAD( "zx405042p__cdi_slave_2.0__b43t__zzmk9213.mc68hc705c8a_withtestrom.7206", 0x0000, 0x2000, CRC(688cda63) SHA1(56d0acd7caad51c7de703247cd6d842b36173079) BAD_DUMP ) + + // Digital Video Cartridge driver ROM. Optional: without it the machine + // simply comes up as a Mono-I with no cartridge attached. The dump is a + // 128KB device recorded twice; either half is the whole ROM. + ROM_REGION(0x40000, "dvc", ROMREGION_ERASE00) + ROM_LOAD_OPTIONAL( "vmpega.rom", 0x000000, 0x40000, CRC(db264e8b) SHA1(be407fbc102f1731a0862554855e963e5a47c17b) ) ROM_END ROM_START( cdi910 ) @@ -476,9 +657,14 @@ ROM_START( cdi490a ) ROM_SYSTEM_BIOS( 0, "cdi490", "CD-i 490" ) ROMX_LOAD( "cdi490a.rom", 0x000000, 0x80000, CRC(e2f200f6) SHA1(c9bf3c4c7e4fe5cbec3fe3fc993c77a4522ca547), ROM_BIOS(0) | ROM_GROUPWORD | ROM_REVERSE ) - ROM_REGION(0x40000, "mpegs", 0) // keep these somewhere - ROM_LOAD( "impega.rom", 0x0000, 0x40000, CRC(84d6f6aa) SHA1(02526482a0851ea2a7b582d8afaa8ef14a8bd914) ) + // The 490 has the digital video hardware built in rather than on a + // cartridge. Only the VMPEG driver ROM is wired up; the IMPEG one is kept + // here because the set contains it, but that chipset is not emulated. + ROM_REGION(0x40000, "dvc", ROMREGION_ERASE00) ROM_LOAD( "vmpega.rom", 0x0000, 0x40000, CRC(db264e8b) SHA1(be407fbc102f1731a0862554855e963e5a47c17b) ) + + ROM_REGION(0x40000, "impeg", 0) + ROM_LOAD( "impega.rom", 0x0000, 0x40000, CRC(84d6f6aa) SHA1(02526482a0851ea2a7b582d8afaa8ef14a8bd914) ) ROM_END diff --git a/src/mame/includes/cdi.h b/src/mame/includes/cdi.h index 067ae303..0326d246 100644 --- a/src/mame/includes/cdi.h +++ b/src/mame/includes/cdi.h @@ -7,6 +7,7 @@ #include "machine/scc68070.h" #include "machine/cdislavehle.h" #include "machine/cdicdic.h" +#include "machine/cdidvc.h" #include "sound/dmadac.h" #include "video/mcd212.h" #include "cpu/m6805/m68hc05.h" @@ -28,6 +29,8 @@ public: , m_servo(*this, "servo") , m_slave(*this, "slave") , m_cdic(*this, "cdic") + , m_dvc(*this, "dvc") + , m_dvc_rom(*this, "dvc") , m_cdrom(*this, "cdrom") , m_mcd212(*this, "mcd212") , m_dmadac(*this, "dac%u", 1U) @@ -35,6 +38,7 @@ public: void cdimono1_base(machine_config &config); void cdimono1(machine_config &config); + void cdimono1_dvc(machine_config &config); void cdimono2(machine_config &config); void cdi910(machine_config &config); @@ -54,6 +58,8 @@ protected: optional_device m_servo; optional_device m_slave; optional_device m_cdic; + optional_device m_dvc; + optional_region_ptr m_dvc_rom; required_device m_cdrom; required_device m_mcd212; @@ -63,6 +69,7 @@ protected: virtual void machine_reset() override ATTR_COLD; void cdimono1_mem(address_map &map) ATTR_COLD; + void cdimono1_dvc_mem(address_map &map) ATTR_COLD; void cdi910_mem(address_map &map) ATTR_COLD; void cdimono2_mem(address_map &map) ATTR_COLD; @@ -76,6 +83,31 @@ protected: uint16_t dvc_r(offs_t offset, uint16_t mem_mask = ~0); void dvc_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + uint16_t dvc_rom_r(offs_t offset); + uint16_t dvc_mpeg_ram_r(offs_t offset, uint16_t mem_mask = ~0); + void dvc_mpeg_ram_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + + uint32_t dvc_video_r(int x, int y); + + // IRQ level 4 is shared between the CDIC and the DVC; a flip-flop on the + // board decides which of them owns the line and therefore which one the + // CPU's interrupt acknowledge is routed to. + enum irq4_owner : uint8_t + { + IRQ4_IDLE, + IRQ4_CDIC, + IRQ4_DVC + }; + + void cdic_intreq_w(int state); + void dvc_intreq_w(int state); + void update_irq4(); + uint8_t iack4_r(); + + uint8_t m_irq4_owner = IRQ4_IDLE; + bool m_cdic_intreq = false; + bool m_dvc_intreq = false; + uint16_t bus_error_r(offs_t offset); void bus_error_w(offs_t offset, uint16_t data); }; diff --git a/src/mame/machine/cdidvc.cpp b/src/mame/machine/cdidvc.cpp new file mode 100644 index 00000000..717dca90 --- /dev/null +++ b/src/mame/machine/cdidvc.cpp @@ -0,0 +1,1516 @@ +// license:GPL-3.0-only +// copyright-holders:Andre Zeps, vibecodekun +/****************************************************************************** + + Philips CD-i Digital Video Cartridge (VMPEG) + + See cdidvc.h for an overview and for the address decode. + + The register file, interrupt behaviour, program-stream demultiplexer and + DMA handshake below are a direct port of the CDi_MiSTer VMPEG core by + Andre Zeps (https://github.com/MiSTer-devel/CDi_MiSTer: rtl/vmpeg.sv, + rtl/mpeg/mpeg_demuxer.sv) and the accompanying notes in doc/dvc.md. + Comments quoting register semantics are from that work, which is + licensed under the GNU General Public License version 3. + +*******************************************************************************/ + +#include "emu.h" +#include "machine/cdidvc.h" + +#include "screen.h" + +#define LOG_REGS (1 << 1) +#define LOG_CMD (1 << 2) +#define LOG_IRQ (1 << 3) +#define LOG_DMA (1 << 4) +#define LOG_STREAM (1 << 5) + +// Set to e.g. (LOG_CMD | LOG_IRQ | LOG_DMA) and define LOG_OUTPUT_FUNC as +// osd_printf_info to trace the driver's conversation with the cartridge. +#define VERBOSE (0) +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(CDI_DVC, cdidvc_device, "cdidvc", "CD-i Digital Video Cartridge") + +cdidvc_device::cdidvc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, CDI_DVC, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , device_video_interface(mconfig, *this) + , m_intreq_callback(*this) + , m_memory_space(*this, ":maincpu", AS_PROGRAM) + , m_scc(*this, ":maincpu") +{ +} + +//************************************************************************** +// device_t implementation +//************************************************************************** + +void cdidvc_device::device_resolve_objects() +{ + m_intreq_callback.resolve_safe(); +} + +void cdidvc_device::device_start() +{ + m_mpeg_ram = std::make_unique(0x80000 / 2); + std::fill_n(m_mpeg_ram.get(), 0x80000 / 2, 0); + + // The cartridge has 512KB of MPEG working RAM; give each decoder a share + // of it as its input buffer so that overflow behaves like the hardware. + m_video.set_buffer_limit(320 * 1024); + m_audio.set_buffer_limit(64 * 1024); + + m_audio_ring.assign(65536, 0); + + m_stream = stream_alloc(0, 2, 44100); + + // FMA/FMV decoder clocks both run at 45 kHz. + m_dclk_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(cdidvc_device::dclk_tick), this)); + m_dclk_timer->adjust(attotime::never); + + // The picture handed to the MCD212 is swapped over between fields, so that + // a field is always scanned out from one picture rather than from whatever + // the decoder happened to be holding as the beam went past. + screen().register_vblank_callback(vblank_state_delegate(&cdidvc_device::vblank_update, this)); + + save_pointer(NAME(m_mpeg_ram), 0x80000 / 2); + + save_item(NAME(m_fma_command)); + save_item(NAME(m_fma_status)); + save_item(NAME(m_fma_isr)); + save_item(NAME(m_fma_ier)); + save_item(NAME(m_fma_ivec)); + save_item(NAME(m_fma_dclk)); + save_item(NAME(m_fma_dclkl_latch)); + save_item(NAME(m_fma_stream)); + save_item(NAME(m_fma_dspa)); + save_item(NAME(m_fma_dsp_enable)); + save_item(NAME(m_fma_stream_change_pending)); + save_item(NAME(m_fma_audio_header)); + + save_item(NAME(m_fmv_isr)); + save_item(NAME(m_fmv_ier)); + save_item(NAME(m_fmv_ivec)); + save_item(NAME(m_fmv_dclk)); + save_item(NAME(m_fmv_syscmd)); + save_item(NAME(m_fmv_vidcmd)); + save_item(NAME(m_fmv_sysscr)); + save_item(NAME(m_fmv_dec_cmd)); + save_item(NAME(m_fmv_vdi_cmd)); + save_item(NAME(m_fmv_frame_rate)); + save_item(NAME(m_fmv_timer_compare)); + save_item(NAME(m_timer_count)); + save_item(NAME(m_fmv_stream)); + save_item(NAME(m_fmv_dsp_enable)); + save_item(NAME(m_fmv_playback_active)); + save_item(NAME(m_fmv_decoder_active)); + save_item(NAME(m_fmv_show)); + save_item(NAME(m_fmv_slow_motion)); + + save_item(NAME(m_fmv_dclk_start_video)); + save_item(NAME(m_fmv_dclk_start_video_latched)); + save_item(NAME(m_fmv_dclk_pause_video)); + save_item(NAME(m_fmv_dclk_pause_video_latched)); + + save_item(NAME(m_regs_update_latch)); + save_item(NAME(m_regs_update_scroll)); + + save_item(NAME(m_ctrl_x_offset)); + save_item(NAME(m_ctrl_y_offset)); + save_item(NAME(m_ctrl_x_active)); + save_item(NAME(m_ctrl_y_active)); + save_item(NAME(m_ctrl_x_display)); + save_item(NAME(m_ctrl_y_display)); + save_item(NAME(m_ctrl_window_width)); + save_item(NAME(m_ctrl_window_height)); + save_item(NAME(m_ctrl_decoder_offset_x)); + save_item(NAME(m_ctrl_decoder_offset_y)); + + save_item(NAME(m_latched_display_x)); + save_item(NAME(m_latched_display_y)); + save_item(NAME(m_latched_window_x)); + save_item(NAME(m_latched_window_y)); + save_item(NAME(m_latched_window_w)); + save_item(NAME(m_latched_window_h)); + + save_item(NAME(m_image_width)); + save_item(NAME(m_image_height)); + save_item(NAME(m_image_rt)); + + save_item(NAME(m_mpeg_ram_unlock_count)); + save_item(NAME(m_mpeg_ram_enabled)); + + save_item(NAME(m_video_sequence.valid)); + save_item(NAME(m_video_sequence.width)); + save_item(NAME(m_video_sequence.height)); + save_item(NAME(m_video_sequence.frame_rate_index)); + save_item(NAME(m_video_sequence.intra_quant)); + save_item(NAME(m_video_sequence.non_intra_quant)); + + save_item(NAME(m_fma_status_decoding)); + save_item(NAME(m_fmv_single_step_pending)); + save_item(NAME(m_video_es_bytes)); + save_item(NAME(m_audio_es_bytes)); + + // Picture pacing and the display side. + save_item(NAME(m_field_count)); + save_item(NAME(m_display_ticks)); + save_item(NAME(m_next_picture_tick)); + save_item(NAME(m_picture_pending)); + save_item(NAME(m_seen_first_picture)); + save_item(NAME(m_video_underflow)); + save_item(NAME(m_display_width)); + save_item(NAME(m_display_height)); + save_item(NAME(m_decoder_timecode)); + save_item(NAME(m_display_timecode)); + save_item(NAME(m_display_video_status)); + + save_item(NAME(m_picture_width)); + save_item(NAME(m_picture_height)); + save_item(NAME(m_picture_updated)); + save_item(NAME(m_shown_width)); + save_item(NAME(m_shown_height)); + save_item(NAME(m_shown_enabled)); + save_item(NAME(m_shown_window_active)); + save_item(NAME(m_shown_display_x)); + save_item(NAME(m_shown_display_y)); + save_item(NAME(m_shown_window_x)); + save_item(NAME(m_shown_window_y)); + save_item(NAME(m_shown_window_w)); + save_item(NAME(m_shown_window_h)); + save_item(NAME(m_shown_picture_save)); + save_item(NAME(m_shown_picture_pixels)); + + // The audio ring is a fixed 65536 entries from device_start onwards, so it + // can be registered where it lies. + save_pointer(NAME(m_audio_ring.data()), m_audio_ring.size()); + save_item(NAME(m_audio_write)); + save_item(NAME(m_audio_read)); + save_item(NAME(m_audio_primed)); + save_item(NAME(m_audio_prime_wait)); + save_item(NAME(m_fma_last_audio_dclk)); + + // The decoders' own state. See device_pre_save()/device_post_load() for how + // it gets in and out of these. + save_item(NAME(m_video_state.buffer_size)); + save_item(NAME(m_video_state.bit_pos)); + save_item(NAME(m_video_state.buffer)); + save_item(NAME(m_video_state.sequence_ended)); + save_item(NAME(m_video_state.picture_type)); + save_item(NAME(m_video_state.temporal_reference)); + save_item(NAME(m_video_state.frames_saved)); + save_item(NAME(m_video_state.past_index)); + save_item(NAME(m_video_state.future_index)); + save_item(NAME(m_video_state.work_index)); + save_item(NAME(m_video_state.display_index)); + save_item(NAME(m_video_state.frame_valid)); + save_item(NAME(m_video_state.frame_picture_type)); + save_item(NAME(m_video_state.frame_temporal_reference)); + save_item(NAME(m_video_state.frame_y)); + save_item(NAME(m_video_state.frame_cb)); + save_item(NAME(m_video_state.frame_cr)); + + save_item(NAME(m_audio_state.buffer_size)); + save_item(NAME(m_audio_state.bit_pos)); + save_item(NAME(m_audio_state.buffer)); + save_item(NAME(m_audio_state.underflow)); + save_item(NAME(m_audio_state.frame_decoded)); + save_item(NAME(m_audio_state.has_header)); + save_item(NAME(m_audio_state.frame_header)); + save_item(NAME(m_audio_state.version)); + save_item(NAME(m_audio_state.layer)); + save_item(NAME(m_audio_state.bitrate_index)); + save_item(NAME(m_audio_state.samplerate_index)); + save_item(NAME(m_audio_state.mode)); + save_item(NAME(m_audio_state.bound)); + save_item(NAME(m_audio_state.sample_rate)); + save_item(NAME(m_audio_state.bit_rate)); + save_item(NAME(m_audio_state.channels)); + save_item(NAME(m_audio_state.samples_per_frame)); + save_item(NAME(m_audio_state.next_frame_size)); + save_item(NAME(m_audio_state.v)); + save_item(NAME(m_audio_state.v_pos)); + + save_item(NAME(m_video_demux_state.state)); + save_item(NAME(m_video_demux_state.packet_body)); + save_item(NAME(m_video_demux_state.length_decreasing)); + save_item(NAME(m_video_demux_state.dts_present)); + save_item(NAME(m_video_demux_state.packet_length)); + save_item(NAME(m_video_demux_state.scr)); + save_item(NAME(m_video_demux_state.scr_temp)); + save_item(NAME(m_video_demux_state.pts)); + save_item(NAME(m_video_demux_state.pts_temp)); + save_item(NAME(m_video_demux_state.dts)); + save_item(NAME(m_video_demux_state.dts_temp)); + save_item(NAME(m_video_demux_state.scr_updated)); + save_item(NAME(m_video_demux_state.pts_updated)); + save_item(NAME(m_video_demux_state.dts_updated)); + save_item(NAME(m_video_demux_state.program_end)); + + save_item(NAME(m_audio_demux_state.state)); + save_item(NAME(m_audio_demux_state.packet_body)); + save_item(NAME(m_audio_demux_state.length_decreasing)); + save_item(NAME(m_audio_demux_state.dts_present)); + save_item(NAME(m_audio_demux_state.packet_length)); + save_item(NAME(m_audio_demux_state.scr)); + save_item(NAME(m_audio_demux_state.scr_temp)); + save_item(NAME(m_audio_demux_state.pts)); + save_item(NAME(m_audio_demux_state.pts_temp)); + save_item(NAME(m_audio_demux_state.dts)); + save_item(NAME(m_audio_demux_state.dts_temp)); + save_item(NAME(m_audio_demux_state.scr_updated)); + save_item(NAME(m_audio_demux_state.pts_updated)); + save_item(NAME(m_audio_demux_state.dts_updated)); + save_item(NAME(m_audio_demux_state.program_end)); + + save_item(NAME(m_fma_playback_start_dclk)); + save_item(NAME(m_fma_playback_start_valid)); + + save_item(NAME(m_dma_pending)); + save_item(NAME(m_dma_for_fma)); + save_item(NAME(m_vcd_pixel_clock)); + save_item(NAME(m_intreq)); +} + +void cdidvc_device::device_reset() +{ + m_fma_command = 0; + m_fma_status = 0; + m_fma_isr = 0; + m_fma_ier = 0; + m_fma_ivec = 0; + m_fma_dclk = 0; + m_fma_dclkl_latch = 0; + m_fma_stream = 0; + m_fma_dspa = 0; + m_fma_dsp_enable = false; + m_fma_stream_change_pending = false; + m_fma_status_decoding = false; + m_fma_playback_start_dclk = 0; + m_fma_playback_start_valid = false; + m_fma_audio_header = 0; + + m_fmv_isr = 0; + m_fmv_ier = 0; + m_fmv_ivec = 0; + m_fmv_dclk = 0; + m_fmv_syscmd = 0; + m_fmv_vidcmd = 0; + m_fmv_sysscr = 0; + m_fmv_dec_cmd = 0; + m_fmv_vdi_cmd = 0; + m_fmv_frame_rate = 0; + m_fmv_timer_compare = 56 - 1; + m_timer_count = 0; + m_fmv_stream = 0; + m_fmv_dsp_enable = false; + m_fmv_playback_active = false; + m_fmv_decoder_active = false; + m_fmv_show = false; + m_fmv_slow_motion = 0; + m_fmv_single_step_pending = false; + + m_fmv_dclk_start_video = 0; + m_fmv_dclk_start_video_latched = false; + m_fmv_dclk_pause_video = 0; + m_fmv_dclk_pause_video_latched = false; + + m_regs_update_latch = false; + m_regs_update_scroll = false; + + m_ctrl_x_offset = m_ctrl_y_offset = 0; + m_ctrl_x_active = m_ctrl_y_active = 0; + m_ctrl_x_display = m_ctrl_y_display = 0; + m_ctrl_window_width = m_ctrl_window_height = 0; + m_ctrl_decoder_offset_x = m_ctrl_decoder_offset_y = 0; + + m_latched_display_x = m_latched_display_y = 0; + m_latched_window_x = m_latched_window_y = 0; + m_latched_window_w = m_latched_window_h = 0; + + m_image_width = m_image_height = m_image_rt = 0; + + // The MPEG working RAM is hidden again after a reset. + m_mpeg_ram_unlock_count = 0; + m_mpeg_ram_enabled = false; + + m_video_demux.reset(); + m_audio_demux.reset(); + m_dma_pending = false; + m_dma_for_fma = false; + m_vcd_pixel_clock = false; + + m_video_es_bytes = 0; + m_audio_es_bytes = 0; + + m_video.reset(); + m_audio.reset(); + m_video_sequence = mpeg1_video_decoder::sequence_state(); + m_display_ticks = 0; + m_next_picture_tick = 0; + m_picture_pending = false; + m_seen_first_picture = false; + m_video_underflow = false; + m_display_width = m_display_height = 0; + m_decoder_timecode = m_display_timecode = 0; + m_display_video_status = 0; + m_picture.clear(); + m_picture_width = m_picture_height = 0; + m_picture_updated = false; + m_shown_picture.clear(); + m_shown_width = m_shown_height = 0; + m_shown_picture_valid = false; + m_shown_enabled = false; + m_shown_window_active = false; + m_shown_display_x = m_shown_display_y = 0; + m_shown_window_x = m_shown_window_y = 0; + m_shown_window_w = m_shown_window_h = 0; + std::fill(m_audio_ring.begin(), m_audio_ring.end(), 0); + m_audio_write = m_audio_read = 0; + m_audio_primed = false; + m_audio_prime_wait = 0; + m_fma_last_audio_dclk = 0; + + m_intreq = false; + m_intreq_callback(0); + + m_dclk_timer->adjust(attotime::from_hz(45000), 0, attotime::from_hz(45000)); +} + +void cdidvc_device::device_pre_save() +{ + // The sequence parameters are normally latched as each picture is shown; + // refresh them here so a state written between two pictures still carries + // the current ones. + m_video.get_sequence_state(m_video_sequence); + + m_video.get_decoder_state(m_video_state); + m_audio.get_decoder_state(m_audio_state); + m_video_demux.get_state(m_video_demux_state); + m_audio_demux.get_state(m_audio_demux_state); + + // The picture on screen. Anything larger than the cap is left out and the + // display picks up again from the next decoded picture. + // + // An invalidated picture is written out as no picture at all. m_shown_picture + // is not cleared when the decoder is switched off - the buffer is recycled + // rather than freed - so the pixel count is the only thing carrying + // m_shown_picture_valid across a state, and saving the pixels of a picture + // that is no longer being shown is what brought the previous clip's last + // frame back on every load. See device_post_load(). + m_shown_picture_pixels = m_shown_picture_valid + ? uint32_t(std::min(m_shown_picture.size(), MAX_SHOWN_PIXELS)) + : 0; + if (m_shown_picture_pixels != 0) + std::copy_n(m_shown_picture.begin(), m_shown_picture_pixels, std::begin(m_shown_picture_save)); + + // Blank whatever the last save left behind the live pixels, so that two runs + // showing the same thing write the same bytes. Rewind stores the difference + // between consecutive states, and run-ahead compares them outright. + std::fill(std::begin(m_shown_picture_save) + m_shown_picture_pixels, + std::end(m_shown_picture_save), 0u); +} + +void cdidvc_device::device_post_load() +{ + // A restored stream resumes part way through a clip, long after the + // sequence header that described it went by, so hand the parameters back + // to the decoder rather than have it discard pictures indefinitely. + // + // This has to come first. set_sequence_state() reallocates and clears the + // frame store whenever the picture size changes, which would throw away the + // reference pictures that set_decoder_state() is about to restore. + m_video.set_sequence_state(m_video_sequence); + + m_video.set_decoder_state(m_video_state); + m_audio.set_decoder_state(m_audio_state); + m_video_demux.set_state(m_video_demux_state); + m_audio_demux.set_state(m_audio_demux_state); + + // Put the shown picture back, so the very first field after a load is drawn + // from the same pixels the saving run was drawing from. + // + // The buffer is sized from m_shown_width/m_shown_height whether or not there + // are any pixels to put in it. vblank_update() swaps this buffer with the + // decoder's back buffer and swaps the dimensions along with it, so a buffer + // that disagrees with its own dimensions ends up handed to the decoder - and + // the decoder reallocates only when the picture size changes, so it would + // then quietly drop every later picture and the display would freeze. + const size_t shown_pixels = size_t(std::max(m_shown_width, 0)) * size_t(std::max(m_shown_height, 0)); + m_shown_picture.assign(shown_pixels, 0); + std::copy_n(m_shown_picture_save, std::min(m_shown_picture_pixels, shown_pixels), + m_shown_picture.begin()); + + // m_shown_picture_valid is deliberately not in the save state - adding an + // item would change the state size and orphan every state written before + // it - so it travels as the pixel count instead: device_pre_save() writes no + // picture when the latched frame has been invalidated, and there being no + // picture to put back is what says so here. + m_shown_picture_valid = m_shown_picture_pixels != 0; + + // The back buffer has to come back allocated to match m_picture_width and + // m_picture_height. Its allocation is keyed off those dimensions rather + // than off the buffer itself, so leaving it empty while they say otherwise + // makes every later picture get dropped instead of shown, and the display + // freezes on whatever was restored here. + if (m_picture_width > 0 && m_picture_height > 0) + m_picture.assign(size_t(m_picture_width) * m_picture_height, 0); + else + m_picture.clear(); + + // Its contents only matter while m_picture_updated is set, and it then + // holds the decoder's current display frame, so rebuild it from there + // rather than carry a second picture's worth of save state. + if (m_picture_updated) + { + if (!m_picture.empty() && m_video.has_display_frame()) + m_video.display_frame_rgb(m_picture.data(), m_picture_width); + else + m_picture_updated = false; + } + +} + +//************************************************************************** +// Interrupts +//************************************************************************** + +void cdidvc_device::update_interrupts() +{ + const bool fma = (m_fma_isr & m_fma_ier) != 0; + const bool fmv = (m_fmv_isr & m_fmv_ier) != 0; + const bool state = fma || fmv; + + if (state != m_intreq) + { + m_intreq = state; + LOGMASKED(LOG_IRQ, "%s: DVC interrupt %s (FMA ISR %04x IER %04x, FMV ISR %04x IER %04x)\n", + machine().describe_context(), state ? "asserted" : "cleared", + m_fma_isr, m_fma_ier, m_fmv_isr, m_fmv_ier); + m_intreq_callback(state ? 1 : 0); + } +} + +uint8_t cdidvc_device::intack_r() +{ + // FMA wins the vector when both subsystems are asserting. The FMV vector + // register holds the level in bits 10..3 rather than the low byte. + if ((m_fma_isr & m_fma_ier) != 0) + return m_fma_ivec & 0xff; + + return (m_fmv_ivec >> 3) & 0xff; +} + +//************************************************************************** +// 45 kHz decoder clock and timer interrupt +//************************************************************************** + +TIMER_CALLBACK_MEMBER(cdidvc_device::dclk_tick) +{ + m_fma_dclk++; + m_fmv_dclk++; + m_display_ticks++; + + // Playback start/pause are scheduled a fixed number of DCLK ticks ahead. + if (m_fmv_dclk_start_video_latched && m_fmv_dclk_start_video == m_fma_dclk) + { + m_fmv_dclk_start_video_latched = false; + m_fmv_playback_active = true; + } + + if (m_fmv_dclk_pause_video_latched && m_fmv_dclk_pause_video == m_fma_dclk) + { + m_fmv_dclk_pause_video_latched = false; + m_fmv_isr |= FMV_INT_PAI; + } + + // The driver programs TCNT for a nominal 10ms tick, but the counter runs + // at 90 kHz / 16, so the achievable period is 100.446... Hz. fmvdrv is + // written around that (its SCR increment of 896 assumes it), so the + // compare below reproduces the hardware divider exactly. + if (m_timer_count >= (uint32_t(m_fmv_timer_compare) << 3) + 7) + { + m_timer_count = 0; + m_fmv_isr |= FMV_INT_TIM; + m_fma_isr |= FMA_INT_POLL; + } + else + { + m_timer_count++; + } + + if (m_fma_playback_start_valid && int64_t(m_fma_dclk) >= m_fma_playback_start_dclk) + { + m_fma_playback_start_valid = false; + m_fma_dsp_enable = true; + m_fma_last_audio_dclk = m_fma_dclk; + } + + // A DMA request that arrived before the 68070 channel was armed is + // retried here; on hardware the device simply keeps REQ asserted. + if (m_dma_pending) + try_dma(); + + // Field rate flag. The driver polls this in the interrupt status register + // while releasing the decoder, so it has to keep ticking regardless of + // whether anything is playing. + const uint32_t field_ticks = m_pal ? (45000 / 50) : (45000 / 60); + bool vsync = false; + if (++m_field_count >= field_ticks) + { + m_field_count = 0; + vsync = true; + m_fmv_isr |= FMV_INT_VSYNC; + } + + // Run the decoders. Video is paced off the stream's own frame period; + // audio is drained as fast as data arrives, into the output ring. + const bool picture_shown = advance_video(); + + // A pending register update is taken either at vertical retrace or when a + // new picture starts being displayed, depending on the scroll bit. Until + // that happens the previously latched geometry stays in force. + if (m_regs_update_latch && ((m_regs_update_scroll && vsync) || (!m_regs_update_scroll && picture_shown))) + { + m_regs_update_latch = false; + + m_latched_display_x = m_ctrl_x_display; + m_latched_display_y = m_ctrl_y_display; + m_latched_window_x = m_ctrl_decoder_offset_x; + m_latched_window_y = m_ctrl_decoder_offset_y; + m_latched_window_w = m_ctrl_window_width; + m_latched_window_h = m_ctrl_window_height; + + // The real hardware always raises DCL alongside VCUP. + m_fmv_isr |= FMV_INT_VCUP | FMV_INT_DCL; + } + + if ((m_fma_dclk & 0x1f) == 0) + advance_audio(); + + update_interrupts(); +} + +//************************************************************************** +// DMA +//************************************************************************** + +void cdidvc_device::request_dma(bool for_fma) +{ + m_dma_pending = true; + m_dma_for_fma = for_fma; + try_dma(); +} + +void cdidvc_device::try_dma() +{ + // The cartridge sits on DMA channel 2 of the 68070 (channel index 1); + // the CDIC owns channel 1. + auto &channel = m_scc->dma().channel[1]; + + const uint32_t count = channel.transfer_counter; + if (!count) + return; + + // Memory-to-device is the only direction that makes sense here. + if (channel.operation_control & OCR_D) + { + LOGMASKED(LOG_DMA, "%s: DVC DMA requested in device-to-memory direction, ignoring\n", + machine().describe_context()); + m_dma_pending = false; + return; + } + + const uint32_t start = channel.memory_address_counter; + + LOGMASKED(LOG_DMA, "%s: DVC DMA %s: %04x words from %06x\n", machine().describe_context(), + m_dma_for_fma ? "FMA" : "FMV", count, start); + + for (uint32_t index = 0; index < count; index++) + { + const uint16_t word = m_memory_space->read_word(start + index * 2); + feed_byte(word >> 8, m_dma_for_fma); + feed_byte(word & 0xff, m_dma_for_fma); + } + + channel.memory_address_counter += count * 2; + channel.transfer_counter = 0; + + // Transfer complete: the device drops its request. For FMA the command + // register's DMA bit is cleared to acknowledge. + m_dma_pending = false; + if (m_dma_for_fma) + m_fma_command &= ~0x8000; + else + m_fmv_syscmd &= ~SYSCMD_DMA; + m_dma_for_fma = false; +} + +void cdidvc_device::feed_byte(uint8_t data, bool for_fma) +{ + if (for_fma) + { + if (m_audio_demux.feed(data, m_fma_stream)) + { + m_audio_es_bytes++; + m_audio.write(&data, 1); + } + + // Playback is scheduled for the moment the decoder clock catches up + // with the first presentation time stamp of the stream. + if (m_audio_demux.pts_updated() && !m_fma_playback_start_valid && !m_fma_dsp_enable) + { + m_fma_playback_start_valid = true; + m_fma_playback_start_dclk = int64_t(m_fma_dclk) + + (m_audio_demux.presentation_timestamp() >> 1) + - (m_audio_demux.system_clock_reference() >> 1); + } + + if (m_audio_demux.program_end()) + { + m_fma_isr |= FMA_INT_EOI; + m_fma_status |= 0x01; + } + } + else + { + if (m_video_demux.feed(data, m_fmv_stream)) + { + m_video_es_bytes++; + m_video.write(&data, 1); + m_video_underflow = false; + } + + if (m_video_demux.dts_updated()) + m_fmv_vdi_cmd |= 1 << 14; + + if (m_video_demux.program_end()) + m_fmv_isr |= FMV_INT_EII; + } +} + +//************************************************************************** +// Decoding +//************************************************************************** + +uint32_t cdidvc_device::audio_ring_used() const +{ + const uint32_t capacity = uint32_t(m_audio_ring.size()); + return (m_audio_write >= m_audio_read) + ? (m_audio_write - m_audio_read) + : (capacity - m_audio_read + m_audio_write); +} + +uint32_t cdidvc_device::audio_ring_free() const +{ + // One slot is always left unused so that full and empty stay distinct. + return uint32_t(m_audio_ring.size()) - audio_ring_used() - 1; +} + +bool cdidvc_device::advance_video() +{ + if (!m_fmv_dsp_enable) + return false; + + // Decode ahead of display so that the picture FIFO count the driver polls + // reflects work already done, as it would on hardware. + if (!m_picture_pending) + { + m_picture_pending = m_video.decode(); + if (m_picture_pending) + m_decoder_timecode = m_video.decoded_timecode(); + + // FMV_IMG follows the decoder, not the display. In the RTL it is loaded + // from decoder_width/height on fmv_event_frame_decoded, and fmvdrv + // depends on that lead: its interrupt handler compares FMV_IMG against + // the size it last published, and on a change hands the new one to the + // title and signals it. Updating FMV_IMG only when a picture starts + // display delays that by the whole of the decoder's head start - half a + // second at the beginning of a clip - so the title spends that long + // working from the previous clip's dimensions. Monty Python's Invasion + // from the Planet Skyron shows it plainly: the title positions each clip + // from the size the driver reports, so it centred a 368x240 clip as if + // it were the 128x96 one before it. + if (m_picture_pending) + { + m_image_width = uint16_t(m_video.width()); + m_image_height = uint16_t(m_video.height()); + m_image_rt = m_video.frame_rate_index(); + } + } + + if (!m_picture_pending) + { + // A clip is terminated by a sequence end code - the FMV driver appends + // one itself when the data runs out - and the decoder only reaches it + // once the last picture has already been decoded and displayed. So at + // the end of every clip the end code turns up on a pass that produces + // no picture, and announcing it only alongside one, as the display path + // below does, means the driver never hears that the clip finished: it + // waits for the end-of-sequence interrupt for ever while the last frame + // stays on screen. + if (m_video.sequence_ended()) + { + m_video.clear_sequence_ended(); + m_fmv_isr |= FMV_INT_ESI | FMV_INT_EOD; + } + + // NDAT reports the moment the video buffer runs dry, not the state of + // being dry. Raising it on every one of the 45 kHz ticks the buffer + // stays empty - which once a clip has ended is for ever - buries the + // 68070 under some 45000 interrupts a second, and the driver never gets + // far enough out of its handler to tear the clip down. The latch + // re-arms in feed_byte() as soon as data arrives again. + if (m_fmv_playback_active && m_video.buffered_bytes() == 0 && !m_video_underflow) + { + m_video_underflow = true; + m_fmv_isr |= FMV_INT_NDAT; + } + return false; + } + + // Hold the picture until its display time arrives. + if (m_fmv_playback_active && !m_fmv_single_step_pending) + { + const uint16_t period = m_video.frame_period_90khz(); + // The pacing counter runs at 45 kHz, half the 90 kHz timestamp clock. + // VMPEG repeats each picture slow_motion + 1 times (1x through 1/8x). + const uint32_t ticks = (period ? (period / 2) : 1875) * (m_fmv_slow_motion + 1); + + if (!m_seen_first_picture) + { + m_seen_first_picture = true; + m_next_picture_tick = m_display_ticks + ticks; + } + else if (int32_t(m_display_ticks - m_next_picture_tick) < 0) + { + return false; + } + else + { + m_next_picture_tick += ticks; + + // Whenever the decoder has fallen more than a frame behind - the + // stream stalled, or a save state put the machine somewhere else - + // picking the schedule up from now is the only sane thing to do. + // Working off the accumulated debt instead would spit out every + // buffered picture at the tick rate until it caught up, which on + // screen is a burst of skipped video. + if (int32_t(m_display_ticks - m_next_picture_tick) >= 0) + m_next_picture_tick = m_display_ticks + ticks; + } + } + else if (!m_fmv_single_step_pending) + { + return false; + } + + // The direct single-picture command is used for isolated scan pictures. + // Normal MPEG playback delays I/P pictures until the following anchor, + // but a scan may supply only one picture before flushing the decoder. + if (m_fmv_single_step_pending && (m_fmv_dec_cmd & 0xff00) == 0x2200) + m_video.display_decoded_frame(); + m_picture_pending = false; + // Decoding an anchor does not necessarily produce an output picture yet. + // In scan mode fmvdrv stops on PIC/GOP, so signalling here before the + // reordered picture exists makes it flush the only anchor and seek again. + // Keep a step request pending across missing-reference pictures as well. + if (!m_video.has_display_frame()) + return false; + m_fmv_single_step_pending = false; + + if (m_video.has_display_frame()) + { + const int width = m_video.width(); + const int height = m_video.height(); + + if (width != m_picture_width || height != m_picture_height) + { + m_picture_width = width; + m_picture_height = height; + m_picture.assign(size_t(width) * height, 0); + } + + if (!m_picture.empty()) + { + m_video.display_frame_rgb(m_picture.data(), width); + m_picture_updated = true; + } + + m_video.get_sequence_state(m_video_sequence); + + m_display_width = uint16_t(width); + m_display_height = uint16_t(height); + m_display_timecode = m_video.display_timecode(); + // fmvdrv uses SYS_VSR's picture type to resync its clock at an I + // picture, and its temporal reference to offset the GOP timecode. + // Report the displayed picture, including when anchors are reordered. + m_display_video_status = (m_video.display_temporal_reference() << 2) | m_video.display_picture_type(); + } + + // A picture starting display is what the driver counts on for pacing. + m_fmv_isr |= FMV_INT_PIC; + + if (m_video.display_picture_type() == 1) + m_fmv_isr |= FMV_INT_GOP | FMV_INT_SEQ; + + if (m_video.sequence_ended()) + { + m_video.clear_sequence_ended(); + m_fmv_isr |= FMV_INT_ESI | FMV_INT_EOD; + } + + return true; +} + +void cdidvc_device::advance_audio() +{ + if (!m_fma_dsp_enable) + return; + + for (;;) + { + // Stop before the output ring fills rather than after. Decoding a + // frame there is nowhere to put simply throws it away, and since the + // driver hands over audio in bursts far faster than real time, that + // silently drops most of a clip. Leaving the frame in the decoder's + // input buffer is also what the hardware does: it is the MPEG buffer + // that backs up, and the driver watches its level. + if (audio_ring_free() < mpeg1_audio_decoder::max_samples_per_frame * 2) + break; + + const int pairs = m_audio.decode(); + if (pairs <= 0) + break; + + if (!m_fma_status_decoding) + { + m_fma_status_decoding = true; + m_fma_status |= FMA_INT_DEC; + m_fma_isr |= FMA_INT_DEC; + } + + m_fma_audio_header = m_audio.frame_header(); + m_fma_status |= FMA_INT_UPD; + m_fma_isr |= FMA_INT_UPD; + + if (m_fma_stream_change_pending) + { + m_fma_isr |= FMA_INT_CSU; + m_fma_stream_change_pending = false; + } + + const int16_t *const src = m_audio.samples(); + const uint32_t capacity = uint32_t(m_audio_ring.size()); + for (int i = 0; i < pairs * 2; i++) + { + const uint32_t next = (m_audio_write + 1) % capacity; + if (next == m_audio_read) + break; // ring full; drop the rest of the frame + m_audio_ring[m_audio_write] = src[i]; + m_audio_write = next; + } + } + + // Anything still sitting in the decoder's input buffer, or already decoded + // and waiting in the output ring, is audio the cartridge has yet to play. + if (m_audio.buffered_bytes() != 0 || audio_ring_used() >= 2) + m_fma_last_audio_dclk = m_fma_dclk; + + if (m_audio.underflowed()) + { + m_audio.clear_underflow(); + + // The decoder raises underflow whenever it does not hold a whole frame, + // which is the normal state between transfers: the driver feeds the + // cartridge just in time and a partial frame is nearly always left over. + // A real underflow is the cartridge's own MPEG buffer running dry, so + // that is what has to be tested here. + // + // Testing buffered_bytes() alone is not enough, because the compressed + // data is consumed here as soon as it arrives and the samples then wait + // in m_audio_ring - an emulator artifact with no hardware counterpart - + // rather than in the buffer the driver is watching. The input buffer + // therefore holds nothing at all every time the byte count happens to + // land on a frame boundary, which for a 192 kbit/s clip is every 400 + // frames, or once every 10.4 seconds. Reporting FMA_INT_UNF there makes + // madriv stop the stream and the FMV player tear down the whole clip, + // including video that is still buffered and playing perfectly well. + // + // So the buffer counts as dry only when there is neither a frame left to + // decode nor any decoded audio left to play, and when it has stayed that + // way for longer than the driver's own refill interval. fmvdrv hands + // over a sector roughly every 100 ms and can be as much as 330 ms late, + // while the cartridge's real 64KB audio buffer would take some 2.7 + // seconds to empty, so half a second sits comfortably between the two. + static constexpr uint32_t DRY_TICKS = 45000 / 2; + + if (m_fma_status_decoding && (m_fma_dclk - m_fma_last_audio_dclk) >= DRY_TICKS) + { + m_fma_status |= FMA_INT_UNF; + m_fma_isr |= FMA_INT_UNF; + m_fma_status &= ~FMA_INT_DEC; + m_fma_status_decoding = false; + m_fma_dsp_enable = false; + m_fma_playback_start_valid = false; + } + } +} + +void cdidvc_device::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) +{ + auto &left = outputs[0]; + auto &right = outputs[1]; + + const uint32_t capacity = uint32_t(m_audio_ring.size()); + + // Rebuild the cushion once the cartridge has stopped and the ring has run + // out, so the next clip starts with a full one. + if (!m_fma_dsp_enable && audio_ring_used() < 2) + { + m_audio_primed = false; + m_audio_prime_wait = 0; + } + + if (!m_audio_primed) + { + if (audio_ring_used() >= 2) + m_audio_prime_wait += uint32_t(left.samples()); + + if (audio_ring_used() < k_audio_prime_samples && m_audio_prime_wait < k_audio_prime_timeout) + { + left.fill(0); + right.fill(0); + return; + } + m_audio_primed = true; + } + + for (int i = 0; i < left.samples(); i++) + { + // Consume whole stereo pairs only. Taking half a pair when the ring + // happens to hold an odd number of samples puts every later sample in + // the wrong channel, and nothing ever puts them back. + if (audio_ring_used() < 2) + { + left.put(i, 0); + right.put(i, 0); + continue; + } + + const int16_t l = m_audio_ring[m_audio_read]; + const int16_t r = m_audio_ring[(m_audio_read + 1) % capacity]; + m_audio_read = (m_audio_read + 2) % capacity; + + left.put_int(i, l, 32768); + right.put_int(i, r, 32768); + } +} + +//************************************************************************** +// Register reads +//************************************************************************** + +uint16_t cdidvc_device::regs_r(offs_t offset, uint16_t mem_mask) +{ + // Only address bits 15..1 are decoded, so the file mirrors every 64KB. + const offs_t reg = offset & 0x7fff; + uint16_t result = 0; + + switch (reg) + { + // ---- FMA ---- + case 0x1800: result = m_fma_command; break; + case 0x1801: result = 0x0200 | m_fma_status; break; + case 0x1802: result = 0x0007; break; + case 0x1803: result = 0x0900; break; + case 0x1804: result = m_fma_stream; break; // wanted stream id + case 0x1805: result = m_fma_stream; break; // current stream id + case 0x1806: result = m_fma_ivec; break; + case 0x1807: result = 0x0042; break; + + case 0x1808: + // Reading the high word latches the low word so the pair is coherent. + result = m_fma_dclk >> 16; + if (!machine().side_effects_disabled()) + m_fma_dclkl_latch = m_fma_dclk & 0xffff; + break; + + case 0x1809: result = m_fma_dclkl_latch; break; + case 0x180a: result = m_fma_audio_header >> 16; break; + case 0x180b: result = m_fma_audio_header & 0xffff; break; + case 0x180c: result = m_fma_dsp_enable ? 1 : 0; break; + + case 0x180d: + result = m_fma_isr; + if (!machine().side_effects_disabled()) + { + m_fma_isr = 0; + update_interrupts(); + } + break; + + case 0x180e: result = m_fma_ier; break; + case 0x1812: result = 0x0004; break; // HF2 flag of the DSP56001 + + // ---- FMV ---- + case 0x2001: result = m_image_width; break; + case 0x2002: result = m_image_height; break; + case 0x2003: result = m_image_rt; break; + case 0x2004: result = m_display_timecode >> 16; break; + case 0x2005: result = m_display_timecode & 0xffff; break; + + case 0x2029: result = m_display_width; break; + case 0x202a: result = m_display_height; break; + case 0x202b: result = m_video.frame_rate_index(); break; + case 0x202c: result = m_decoder_timecode >> 16; break; + case 0x202d: result = m_decoder_timecode & 0xffff; break; + case 0x202e: result = m_display_video_status; break; + case 0x202f: result = m_video.buffer_full() ? 0 : 0x2000; break; // SYS_STS + + case 0x2030: result = m_fmv_ier; break; + + case 0x2031: + result = m_fmv_isr; + if (!machine().side_effects_disabled()) + { + m_fmv_isr = 0; + update_interrupts(); + } + break; + + case 0x2032: result = m_fmv_timer_compare; break; + + case 0x2036: result = m_ctrl_y_offset; break; + case 0x2037: result = m_ctrl_x_offset; break; + case 0x2038: result = m_ctrl_y_active; break; + case 0x2039: result = m_ctrl_x_active; break; + case 0x203a: result = m_ctrl_y_display; break; + case 0x203b: result = m_ctrl_x_display; break; + case 0x203c: result = m_ctrl_window_height; break; + case 0x203d: result = m_ctrl_window_width; break; + case 0x203e: result = m_ctrl_decoder_offset_y; break; + case 0x203f: result = m_ctrl_decoder_offset_x; break; + + case 0x2044: result = m_fmv_dec_cmd; break; + case 0x2046: result = m_fmv_vdi_cmd; break; + case 0x2049: result = 0; break; // GEN_VID_BUF + case 0x204c: result = (m_fmv_dclk >> 6) & 0xffff; break; // GEN_SYSCR + case 0x204e: result = 0; break; // reads 0 on real hardware + case 0x204f: result = 1; break; + + case 0x2050: + // GEN_DEC_TIM1: the DTS as the CPU sees it, at 703.125 Hz resolution. + result = uint16_t((m_video_demux.decoding_timestamp() >> 7) & 0x7fff); + break; + + case 0x2052: result = uint16_t(m_video.pictures_buffered()); break; + case 0x2054: result = m_video.frame_period_90khz(); break; + case 0x2055: result = m_pal ? 0x0708 : 0x05dc; break; // display rate + case 0x2056: result = m_fmv_frame_rate; break; + case 0x2060: result = m_fmv_syscmd; break; + case 0x2061: result = m_fmv_vidcmd; break; + case 0x2062: result = m_fmv_stream; break; + case 0x206e: result = m_fmv_ivec; break; + case 0x2073: result = 0; break; // MMU base pointer, ignored + + default: + LOGMASKED(LOG_REGS, "%s: DVC read of unhandled register %04x & %04x\n", + machine().describe_context(), reg * 2, mem_mask); + break; + } + + LOGMASKED(LOG_REGS, "%s: DVC read %06x = %04x & %04x\n", machine().describe_context(), + 0xe00000 + reg * 2, result, mem_mask); + + return result; +} + +//************************************************************************** +// Register writes +//************************************************************************** + +void cdidvc_device::write_fma_cmd(uint16_t data) +{ + LOGMASKED(LOG_CMD, "%s: FMA CMD = %04x\n", machine().describe_context(), data); + + m_fma_command = data; + + if (BIT(data, 15)) + { + // Green book 8.2.4.3.3: a transfer clears the underflow status. + m_fma_status &= ~FMA_INT_UNF; + request_dma(true); + } + + if (BIT(data, 0)) + { + // Stop + m_fma_dsp_enable = false; + m_fma_status = 0; + m_fma_status_decoding = false; + m_fma_playback_start_valid = false; + m_fma_last_audio_dclk = m_fma_dclk; + m_audio_demux.reset(); + m_audio.reset(); + } +} + +void cdidvc_device::write_syscmd(uint16_t data) +{ + LOGMASKED(LOG_CMD, "%s: FMV SYSCMD = %04x\n", machine().describe_context(), data); + + m_fmv_syscmd = data; + + if (data & SYSCMD_PLAY) + { + m_fmv_dclk_start_video = m_fma_dclk + 1000; + m_fmv_dclk_start_video_latched = true; + m_fmv_dec_cmd |= 0x42; + m_fmv_slow_motion = data & 0x07; + m_fmv_decoder_active = true; + m_seen_first_picture = false; + m_video_underflow = false; + } + + if (data & SYSCMD_PAUSE) + { + m_fmv_playback_active = false; + m_fmv_dclk_start_video_latched = false; + m_fmv_dclk_pause_video = m_fma_dclk + 100; + m_fmv_dclk_pause_video_latched = true; + } + + if (data & SYSCMD_CONTINUE) + { + m_fmv_dclk_start_video = m_fma_dclk + 1000; + m_fmv_dclk_start_video_latched = true; + m_fmv_slow_motion = data & 0x07; + } + + if (data & SYSCMD_STEP) + { + m_fmv_dclk_start_video_latched = false; + m_fmv_single_step_pending = true; + } + + if (data & SYSCMD_STOP) + { + m_fmv_single_step_pending = false; + m_fmv_playback_active = false; + m_fmv_dclk_start_video_latched = false; + m_fmv_decoder_active = false; + } + + if (data & SYSCMD_CLEAR_FIFO) + { + m_fmv_single_step_pending = false; + m_fmv_playback_active = false; + m_fmv_dclk_start_video_latched = false; + m_video_demux.reset(); + // FIFO clear invalidates compressed data and prediction references, + // but VMPEG keeps its persistent sequence configuration until decoder + // off. A seek need not supply another sequence header (The Firm). + m_video.clear_fifo(); + m_picture_pending = false; + m_seen_first_picture = false; + m_video_underflow = false; + // The decoder is bounced off and straight back on again. + m_fmv_dsp_enable = true; + } + + if (data & SYSCMD_DEC_ON) + { + m_fmv_dsp_enable = true; + LOGMASKED(LOG_CMD, "%s: FMV decoder on\n", machine().describe_context()); + } + + if (data & SYSCMD_DEC_OFF) + { + m_fmv_single_step_pending = false; + m_fmv_dsp_enable = false; + m_fmv_playback_active = false; + m_fmv_dclk_start_video_latched = false; + m_fmv_decoder_active = false; + m_image_width = 0; + m_image_height = 0; + m_image_rt = 0; + m_video_demux.reset(); + m_video.reset(); + m_picture_pending = false; + m_seen_first_picture = false; + m_video_underflow = false; + // Decoder off is what the RTL calls resetting persistent storage, and + // it invalidates the latched display frame. Drop the back buffer's + // pending update along with it, or a picture decoded just before the + // teardown would be swapped in afterwards and count as this clip's. + m_shown_picture_valid = false; + m_picture_updated = false; + LOGMASKED(LOG_CMD, "%s: FMV decoder off\n", machine().describe_context()); + } + + if (data & SYSCMD_DMA) + request_dma(false); +} + +void cdidvc_device::write_vidcmd(uint16_t data) +{ + LOGMASKED(LOG_CMD, "%s: FMV VIDCMD = %04x\n", machine().describe_context(), data); + + m_fmv_vidcmd = data; + + if (BIT(data, 3)) // RegsUpd + { + m_regs_update_latch = true; + m_regs_update_scroll = BIT(data, 2); + } + + if (BIT(data, 8)) // Hide + m_fmv_show = false; + + if (BIT(data, 9)) // Show + m_fmv_show = true; + + if (BIT(data, 10)) // Show on next picture change + m_fmv_show = true; +} + +void cdidvc_device::regs_w(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + const offs_t reg = offset & 0x7fff; + + LOGMASKED(LOG_REGS, "%s: DVC write %06x = %04x & %04x\n", machine().describe_context(), + 0xe00000 + reg * 2, data, mem_mask); + + // The MPEG working RAM only becomes visible once the driver has done + // enough register traffic to prove it is really there. Hardware uses a + // six-bit counter; until it wraps, the OS-9 RAM crawler must not be able + // to see the memory. + if (m_mpeg_ram_unlock_count == 0x3f) + { + if (!m_mpeg_ram_enabled) + LOGMASKED(LOG_REGS, "%s: DVC MPEG RAM unlocked\n", machine().describe_context()); + m_mpeg_ram_enabled = true; + } + m_mpeg_ram_unlock_count = (m_mpeg_ram_unlock_count + 1) & 0x3f; + + // VMPEG pixel clock select lives at e01xxx. + if ((reg & 0x7f00) == 0x1000) + { + m_vcd_pixel_clock = BIT(data, 0); + return; + } + + switch (reg) + { + // ---- FMA ---- + case 0x1800: write_fma_cmd(data); break; + + case 0x1802: + // The VMPEG ROM writes an immediate 7 here right after setting the + // FMA stream number; purpose unknown, no effect modelled. + break; + + case 0x1804: + m_fma_stream = data & 0x0f; + m_fma_stream_change_pending = true; + break; + + case 0x1806: m_fma_ivec = data; break; + case 0x180c: break; // FMA RUN + + case 0x180e: + m_fma_ier = data; + update_interrupts(); + break; + + case 0x1811: m_fma_dspa = data & 0xff; break; + case 0x1812: break; // FMA DSPD + + // ---- FMV ---- + case 0x2001: m_image_width = data; break; + case 0x2002: m_image_height = data; break; + case 0x2003: m_image_rt = data; break; + + case 0x2030: + m_fmv_ier = data; + update_interrupts(); + break; + + case 0x2031: break; // ISR is read-to-clear + case 0x2032: m_fmv_timer_compare = data; break; + + case 0x2036: m_ctrl_y_offset = data; break; + case 0x2037: m_ctrl_x_offset = data; break; + case 0x2038: m_ctrl_y_active = data; break; + case 0x2039: m_ctrl_x_active = data; break; + case 0x203a: m_ctrl_y_display = data; break; + case 0x203b: m_ctrl_x_display = data; break; + case 0x203c: m_ctrl_window_height = data; break; + case 0x203d: m_ctrl_window_width = data; break; + case 0x203e: m_ctrl_decoder_offset_y = data; break; + case 0x203f: m_ctrl_decoder_offset_x = data; break; + + case 0x2044: + m_fmv_dec_cmd = data; + // GEN_DEC_CMD 22xx requests a single picture (also used by fmvdrv + // for fast forward/reverse). As in VMPEG, an active pause command + // inhibits this request. Complete it through the normal picture + // display path so that PIC and pending clip updates are delivered. + if ((data & 0xff00) == 0x2200 && !(m_fmv_syscmd & SYSCMD_PAUSE)) + m_fmv_single_step_pending = true; + break; + case 0x2046: m_fmv_vdi_cmd = data; break; + case 0x2049: break; // GEN_VID_BUF + + case 0x204c: + // GEN_SYSCR: only bits 21..6 of the 45 kHz clock are CPU-writable. + m_fmv_dclk = (m_fmv_dclk & ~(0xffffu << 6)) | (uint32_t(data) << 6); + break; + + case 0x2056: m_fmv_frame_rate = data; break; + case 0x2057: m_timer_count = 0; break; // TRLD + case 0x2060: write_syscmd(data); break; + case 0x2061: write_vidcmd(data); break; + case 0x2062: m_fmv_stream = data & 0x0f; break; + case 0x2063: m_fmv_sysscr = data; break; + case 0x206e: m_fmv_ivec = data; break; + + case 0x206f: + // XFER: programmed-I/O alternative to DMA. Each write pushes one + // 16-bit word of program stream into the video path. + feed_byte(data >> 8, false); + feed_byte(data & 0xff, false); + break; + + default: + LOGMASKED(LOG_REGS, "%s: DVC write to unhandled register %04x = %04x\n", + machine().describe_context(), reg * 2, data); + break; + } +} + +//************************************************************************** +// MPEG working RAM +//************************************************************************** + +uint16_t cdidvc_device::mpeg_ram_r(offs_t offset, uint16_t mem_mask) +{ + return m_mpeg_ram[offset & 0x3ffff]; +} + +void cdidvc_device::mpeg_ram_w(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + COMBINE_DATA(&m_mpeg_ram[offset & 0x3ffff]); +} + +//************************************************************************** +// Video output +//************************************************************************** + +void cdidvc_device::vblank_update(screen_device &screen, bool state) +{ + // Only on the way into vertical blanking: what is latched here is what the + // whole of the next field will be drawn from. + if (!state) + return; + + // The picture buffers are swapped rather than copied; the decoder always + // writes a complete picture, so whatever is left in the buffer it gets + // back does not matter. + if (m_picture_updated) + { + m_picture_updated = false; + m_picture.swap(m_shown_picture); + std::swap(m_picture_width, m_shown_width); + std::swap(m_picture_height, m_shown_height); + m_shown_picture_valid = true; + } + + // The geometry has to move at the same moment as the picture, or a field + // ends up drawn half at the old offset and half at the new one. + m_shown_enabled = m_fmv_show && m_fmv_dsp_enable; + m_shown_display_x = m_latched_display_x; + m_shown_display_y = m_latched_display_y; + m_shown_window_x = m_latched_window_x; + m_shown_window_y = m_latched_window_y; + + // What the cartridge displays is the decoded picture, taken from the DECOFF + // corner and placed at SCRPOS. DECWIN does not narrow it further. + // + // DECWIN is not something titles program directly. fmvdrv keeps a display + // window in its own state, hands it to the cartridge whenever it updates the + // registers, and - this is the important part - only ever *shrinks* it: the + // interrupt handler clips the window to the picture size every time a clip + // decodes at a new size (fmvdrv PSWndw/e541ca, and the clip at e5439a), and + // nothing in the driver ever grows it again. So once a title has played one + // small clip, the driver's window stays that small for every clip after it. + // + // Monty Python's Invasion from the Planet Skyron shows what that costs. It + // alternates 128x96 inserts with 368x240 full-screen clips and never calls + // mv_window() itself, so the window is always the *previous* clip's size: + // the register trace has the driver programming DECWIN 128x96 for the + // 368x240 "INTERMISSION" card (which then appears as a corner of itself), + // and DECWIN 268x175 for a 128x96 card (which then sits in a black surround + // covering the artwork behind it). Both are the same bug seen from the two + // sides. Since fmvdrv is Philips' own driver and the title is Philips' own + // content, hardware cannot be taking DECWIN as a hard crop the way the + // MiSTer frameplayer does - its shrink-only bookkeeping would break every + // title that changes picture size, not just this one. + // + // The window still bounds where the cartridge is keyed in over the CD-i + // planes, which is what Mutant Rampage needs for the border it draws around + // its MPEG window; that title programs DECWIN to exactly its picture size, + // so deriving the window from the picture leaves it unchanged. + const int avail_w = m_shown_width - int(m_latched_window_x); + const int avail_h = m_shown_height - int(m_latched_window_y); + m_shown_window_w = uint16_t(std::max(avail_w, 0)); + m_shown_window_h = uint16_t(std::max(avail_h, 0)); + + m_shown_window_active = m_fmv_dsp_enable && m_shown_window_w != 0 && m_shown_window_h != 0; +} + +bool cdidvc_device::video_enabled() const +{ + return m_shown_enabled; +} + +bool cdidvc_device::window_covers(int x, int y) const +{ + if (!m_shown_window_active) + return false; + + const int px = x - int(m_shown_display_x); + const int py = y - int(m_shown_display_y); + + return px >= 0 && py >= 0 && px < int(m_shown_window_w) && py < int(m_shown_window_h); +} + +bool cdidvc_device::get_pixel(int x, int y, uint32_t &result) const +{ + if (!m_shown_enabled || !m_shown_picture_valid || m_shown_picture.empty()) + return false; + + // The picture is placed at the display offset latched at the last register + // update, and runs from the decode offset to the end of the picture. + const int px = x - int(m_shown_display_x); + const int py = y - int(m_shown_display_y); + + if (px < 0 || py < 0) + return false; + + if (px >= int(m_shown_window_w) || py >= int(m_shown_window_h)) + return false; + + // Within the window, the source pixel is offset by the decode offset. + const int sx = px + int(m_shown_window_x); + const int sy = py + int(m_shown_window_y); + + if (sx < 0 || sy < 0 || sx >= m_shown_width || sy >= m_shown_height) + return false; + + result = m_shown_picture[size_t(sy) * m_shown_width + sx]; + return true; +} diff --git a/src/mame/machine/cdidvc.h b/src/mame/machine/cdidvc.h new file mode 100644 index 00000000..bb4efdb7 --- /dev/null +++ b/src/mame/machine/cdidvc.h @@ -0,0 +1,401 @@ +// license:GPL-3.0-only +// copyright-holders:Andre Zeps, vibecodekun +/****************************************************************************** + + Philips CD-i Digital Video Cartridge (VMPEG) + -------------------------------------------- + + The DVC is an expansion cartridge that adds MPEG-1 audio and video decoding + to a CD-i player. Several chipsets exist; this device models the VMPEG + variant (as fitted to the 22ER9141 cartridge and built into the CD-i 490), + which pairs an MCD251 video decoder with a DSP56001 doing audio in software. + + The register-level behaviour modelled here was derived from the + documentation and RTL of the CDi_MiSTer project by Andre Zeps, which + reverse-engineered the cartridge in detail: + + https://github.com/MiSTer-devel/CDi_MiSTer (doc/dvc.md, rtl/vmpeg.sv) + + CDi_MiSTer is licensed under the GNU General Public License version 3, + and this device is a port of it, so cdidvc.cpp/.h and mpeg1demux.cpp/.h + are distributed under the same license. + + Address decode (as seen from the 68070): + + d00000-dfffff 1MB of extra system RAM, always visible + e00000-e3ffff VMPEG registers (only address bits 15..1 are decoded, + so the register file mirrors every 64KB) + e40000-e7ffff 128KB driver ROM (fmvdrv/madriv OS-9 modules), mirrored + e80000-efffff 512KB MPEG working RAM, hidden until unlocked + + The MPEG RAM is deliberately not visible after reset. The OS-9 memory + crawler would otherwise find it and hand it out as general system RAM, + which breaks the decoder. Hardware gates it behind a counter that unlocks + once the driver has performed 64 register writes. + + TODO: + + - Picture display is paced off the sequence's own frame rate rather than + the decoding time stamps, so a stream whose audio and video drift apart + will not be resynchronised. + +*******************************************************************************/ + +#ifndef MAME_MACHINE_CDIDVC_H +#define MAME_MACHINE_CDIDVC_H + +#pragma once + +#include "machine/scc68070.h" +#include "machine/mpeg1demux.h" +#include "machine/mpeg1video.h" +#include "machine/mpeg1audio.h" + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +class cdidvc_device : public device_t, public device_sound_interface, public device_video_interface +{ +public: + cdidvc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + auto intreq_callback() { return m_intreq_callback.bind(); } + + // e00000-e3ffff register window + uint16_t regs_r(offs_t offset, uint16_t mem_mask = ~0); + void regs_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + + // e80000-efffff MPEG working RAM + uint16_t mpeg_ram_r(offs_t offset, uint16_t mem_mask = ~0); + void mpeg_ram_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + bool mpeg_ram_enabled() const { return m_mpeg_ram_enabled; } + + uint8_t intack_r(); + bool intreq() const { return m_intreq; } + + void set_pal(bool pal) { m_pal = pal; } + + // Video output to the MCD212 external-video (backdrop) path. + // + // window_covers() reports whether the cartridge's display window is open + // over a pixel, which is a separate question from whether there is a + // picture to put there: the window stays open across the gap between clips, + // and the CD-i plane behind it stays keyed away, so what shows is black + // rather than the plane. get_pixel() returns false when there is nothing + // to display, and the caller should then fall back to black. + // + // The window is the decoded picture, taken from the DECOFF corner; DECWIN + // does not narrow it, because fmvdrv leaves it holding the *previous* + // clip's size. vblank_update() has the detail. + bool video_enabled() const; + bool window_covers(int x, int y) const; + bool get_pixel(int x, int y, uint32_t &result) const; + +protected: + virtual void device_resolve_objects() override; + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + virtual void sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) override; + + virtual void device_pre_save() override; + virtual void device_post_load() override; + + TIMER_CALLBACK_MEMBER(dclk_tick); + +private: + void vblank_update(screen_device &screen, bool state); + + // FMV interrupt status/enable bits. Bit numbering follows the packed + // struct in the MiSTer RTL, where the first-declared field is the MSB. + enum : uint16_t + { + FMV_INT_SEQ = 1 << 0, // sequence header decoded + FMV_INT_GOP = 1 << 1, // group-of-pictures decoded + FMV_INT_PIC = 1 << 2, // picture started display + FMV_INT_EOD = 1 << 3, // end of data + FMV_INT_RFB = 1 << 4, // request for bits + FMV_INT_NDAT = 1 << 5, // no data / underflow + FMV_INT_OVF = 1 << 6, // overflow + FMV_INT_DCL = 1 << 7, + FMV_INT_TIM = 1 << 8, // timer + FMV_INT_ESI = 1 << 9, // end sequence indicator + FMV_INT_EII = 1 << 10, // end ISO indicator + FMV_INT_VSYNC = 1 << 11, + FMV_INT_PAI = 1 << 12, // pause + FMV_INT_VCUP = 1 << 13, // video clip update + FMV_INT_ERDD = 1 << 14, + FMV_INT_ERDV = 1 << 15 + }; + + // FMA interrupt status/enable bits + enum : uint16_t + { + FMA_INT_EOI = 1 << 0, // ISO end detected + FMA_INT_CSU = 1 << 1, // audio stream changed + FMA_INT_UPD = 1 << 2, // frame header updated + FMA_INT_UNF = 1 << 3, // underflow + FMA_INT_DEC = 1 << 4, // decoding started + FMA_INT_ERR = 1 << 5, + FMA_INT_POLL = 1 << 8 + }; + + // FMV SYSCMD (e040c0) command bits + enum : uint16_t + { + SYSCMD_PLAY = 1 << 3, + SYSCMD_PAUSE = 1 << 4, + SYSCMD_CONTINUE = 1 << 5, + SYSCMD_STEP = 1 << 6, + SYSCMD_STOP = 1 << 7, + SYSCMD_CLEAR_FIFO = 1 << 8, + SYSCMD_SEARCH_GOP = 1 << 10, + SYSCMD_DEC_ON = 1 << 12, + SYSCMD_DEC_OFF = 1 << 13, + SYSCMD_DMA = 1 << 15 + }; + + void update_interrupts(); + uint32_t audio_ring_used() const; + uint32_t audio_ring_free() const; + void request_dma(bool for_fma); + void try_dma(); + void feed_byte(uint8_t data, bool for_fma); + bool advance_video(); + void advance_audio(); + void write_syscmd(uint16_t data); + void write_vidcmd(uint16_t data); + void write_fma_cmd(uint16_t data); + + devcb_write_line m_intreq_callback; + + required_address_space m_memory_space; + required_device m_scc; + + emu_timer *m_dclk_timer = nullptr; + + bool m_pal = true; + + // ---- FMA (audio) registers ---- + uint16_t m_fma_command = 0; + uint8_t m_fma_status = 0; + uint16_t m_fma_isr = 0; + uint16_t m_fma_ier = 0; + uint16_t m_fma_ivec = 0; + uint32_t m_fma_dclk = 0; + uint16_t m_fma_dclkl_latch = 0; + uint8_t m_fma_stream = 0; + uint8_t m_fma_dspa = 0; + bool m_fma_dsp_enable = false; + bool m_fma_stream_change_pending = false; + bool m_fma_status_decoding = false; + // The audio subsystem starts decoding when the stream's own clock reaches + // the first presentation time stamp, rather than on an explicit command. + int64_t m_fma_playback_start_dclk = 0; + bool m_fma_playback_start_valid = false; + uint32_t m_fma_audio_header = 0; + + // ---- FMV (video) registers ---- + uint16_t m_fmv_isr = 0; + uint16_t m_fmv_ier = 0; + uint16_t m_fmv_ivec = 0; + uint32_t m_fmv_dclk = 0; + uint16_t m_fmv_syscmd = 0; + uint16_t m_fmv_vidcmd = 0; + uint16_t m_fmv_sysscr = 0; + uint16_t m_fmv_dec_cmd = 0; + uint16_t m_fmv_vdi_cmd = 0; + uint16_t m_fmv_frame_rate = 0; + uint16_t m_fmv_timer_compare = 56 - 1; + uint32_t m_timer_count = 0; + uint8_t m_fmv_stream = 0; + bool m_fmv_dsp_enable = false; + bool m_fmv_playback_active = false; + bool m_fmv_decoder_active = false; + bool m_fmv_show = false; + bool m_fmv_single_step_pending = false; + uint8_t m_fmv_slow_motion = 0; + + uint32_t m_fmv_dclk_start_video = 0; + bool m_fmv_dclk_start_video_latched = false; + uint32_t m_fmv_dclk_pause_video = 0; + bool m_fmv_dclk_pause_video_latched = false; + + bool m_regs_update_latch = false; + bool m_regs_update_scroll = false; + + // mv_org()/mv_pos()/mv_window() geometry, written by the driver + uint16_t m_ctrl_x_offset = 0, m_ctrl_y_offset = 0; + uint16_t m_ctrl_x_active = 0, m_ctrl_y_active = 0; + uint16_t m_ctrl_x_display = 0, m_ctrl_y_display = 0; + uint16_t m_ctrl_window_width = 0, m_ctrl_window_height = 0; + uint16_t m_ctrl_decoder_offset_x = 0, m_ctrl_decoder_offset_y = 0; + + // Latched at register-update time, these are what the display uses. + // m_latched_window_w/h are the DECWIN size; the display path does not use + // it - see vblank_update() for why - but it is latched all the same, since + // the register still has to read back the way the hardware's does. + uint16_t m_latched_display_x = 0, m_latched_display_y = 0; + uint16_t m_latched_window_x = 0, m_latched_window_y = 0; + uint16_t m_latched_window_w = 0, m_latched_window_h = 0; + + uint16_t m_image_width = 0, m_image_height = 0, m_image_rt = 0; + + // ---- unlock counter for the MPEG working RAM ---- + uint8_t m_mpeg_ram_unlock_count = 0; + bool m_mpeg_ram_enabled = false; + std::unique_ptr m_mpeg_ram; + + // ---- stream plumbing ---- + mpeg1_demuxer m_video_demux{ 0xe0 }; + mpeg1_demuxer m_audio_demux{ 0xc0 }; + bool m_dma_pending = false; + bool m_dma_for_fma = false; + bool m_vcd_pixel_clock = false; + + bool m_intreq = false; + + // Counts of elementary-stream bytes handed to each decoder; useful when + // tracing whether the driver is actually streaming. + uint64_t m_video_es_bytes = 0; + uint64_t m_audio_es_bytes = 0; + + // --- decoders --- + // + // The decoders are kept free of MAME, so they hand their state over as + // plain structs that this device owns, registers and copies across in + // device_pre_save()/device_post_load(). Between them they add rather more + // than a megabyte to the save state, most of it the two reference pictures + // and the input FIFOs. + mpeg1_video_decoder::sequence_state m_video_sequence; + mpeg1_video_decoder::decoder_state m_video_state; + mpeg1_audio_decoder::decoder_state m_audio_state; + mpeg1_demuxer::demux_state m_video_demux_state; + mpeg1_demuxer::demux_state m_audio_demux_state; + mpeg1_video_decoder m_video; + mpeg1_audio_decoder m_audio; + + // Pictures are pulled out of the decoder on the stream's own frame period. + // + // The deadline is measured against m_display_ticks, a plain count of 45 kHz + // ticks, and deliberately not against m_fmv_dclk. GEN_SYSCR lets the driver + // write the stream's system clock reference straight into m_fmv_dclk, so + // that register jumps backwards and forwards with the stream; pacing off it + // leaves the deadline unreachable and the picture never changes again. + uint32_t m_field_count = 0; + uint32_t m_display_ticks = 0; + uint32_t m_next_picture_tick = 0; + bool m_picture_pending = false; + bool m_seen_first_picture = false; + + // Latch behind FMV_INT_NDAT, so that the video buffer running dry is + // reported once per occurrence rather than on every tick it stays empty. + bool m_video_underflow = false; + + // Display geometry reported back to the driver. + uint16_t m_display_width = 0, m_display_height = 0; + uint32_t m_decoder_timecode = 0, m_display_timecode = 0; + uint8_t m_display_video_status = 0; + + // Decoded picture, already converted to RGB for the MCD212 to pick up. + // + // The MCD212 is configured for VIDEO_UPDATE_SCANLINE, so it asks for the + // cartridge's pixels one scanline at a time as the field is scanned out, + // while the decoder replaces the picture whenever the 45 kHz clock says the + // next one is due. Handing get_pixel() the buffer the decoder writes into + // therefore splices two different pictures - and two different sets of + // geometry - into one displayed field, which is what shows up as tearing. + // + // So the decoder writes into m_picture and everything the display reads + // lives in the m_shown_* copies, swapped over once per field in + // vblank_update(). + std::vector m_picture; + int m_picture_width = 0, m_picture_height = 0; + bool m_picture_updated = false; + + std::vector m_shown_picture; + int m_shown_width = 0, m_shown_height = 0; + + // Whether the picture on the display side is one this clip put there. + // + // This is the frameplayer's latched_frame_valid: the cartridge shows a + // picture only once one has been latched for display, and switching the + // decoder off invalidates whatever was latched. Without it the previous + // clip's last frame stays in the display buffer and is put back on screen + // the moment the driver re-enables the window for the *next* clip, a couple + // of fields before that clip's first picture is ready - which is seen as + // the outgoing clip's final frame, often at the wrong position, flashing up + // again just before the new one starts. + // + // It has to survive a save state, or rewind and run-ahead - which load one + // every frame - put the stale frame back on screen as fast as this clears + // it. See device_pre_save() for how it travels. + bool m_shown_picture_valid = false; + + // The shown picture has to ride along in the save state. It is derived from + // the decoder's frame store, but not reconstructably so: by the time a + // state is written the decoder has usually moved on to a newer picture and + // the one on screen may already have been recycled. + // + // The pixel count doubles as m_shown_picture_valid, which has no save item + // of its own: an invalidated picture is written out as no picture at all. + // + // The back buffer m_picture needs no such copy. It only ever matters while + // m_picture_updated is set, and it then holds exactly the decoder's current + // display frame, so device_post_load() rebuilds it instead. + static constexpr size_t MAX_SHOWN_PIXELS = 384 * 288; + uint32_t m_shown_picture_save[MAX_SHOWN_PIXELS] = { 0 }; + uint32_t m_shown_picture_pixels = 0; + bool m_shown_enabled = false; + // The display window outlives the picture: it is open whenever the decoder + // is powered up and the driver has given it a size, whether or not a clip + // is currently running through it. + bool m_shown_window_active = false; + uint16_t m_shown_display_x = 0, m_shown_display_y = 0; + uint16_t m_shown_window_x = 0, m_shown_window_y = 0; + uint16_t m_shown_window_w = 0, m_shown_window_h = 0; + + // Audio output ring, drained by the sound stream. + // + // Playback holds off until the ring has k_audio_prime_samples in it. The + // driver hands the cartridge audio just in time - it keeps on the order of + // 120ms of it outstanding - while the sound stream drains a whole update at + // a time, so with no cushion the ring comes up a few hundred samples short + // on the trough of every delivery cycle. The stream then has to insert + // silence, and those short gaps are what is heard as crackling. The cushion + // is rebuilt whenever the cartridge stops and drains, so it costs its own + // depth in latency once per clip rather than accumulating. + // + // The cushion has to be deeper than one delivery burst, or the first burst + // satisfies it and it buys nothing. How deep that has to be depends on the + // title: Mutant Rampage hands over about 95ms at a time, but Monty Python's + // Invasion from the Planet Skyron opens a clip by delivering 185ms every + // 280ms - fully caught up on average, but a third of each cycle spent dry - + // and keeps that up for the first four seconds. A 150ms cushion is emptied + // by the trough of every one of those cycles, which is heard as the clicking + // and stuttering that clears up once the driver settles into a tighter + // rhythm. Measured against that title's delivery, 250ms is the point where + // the ring stops reaching zero at all; 300ms leaves some margin and still + // costs well under half the ring's 743ms depth. + // + // A title that keeps less audio outstanding than the cushion would never + // reach it and would play nothing at all, so once audio is actually arriving + // the wait is given up on after this long and playback starts with whatever + // has accumulated. The timer only runs while the ring has something in it: + // before that there is no audio yet and nothing is lost by waiting. + static constexpr uint32_t k_audio_prime_samples = (44100 * 300 / 1000) * 2; // 300ms, stereo + static constexpr uint32_t k_audio_prime_timeout = 44100 / 2; // 500ms + sound_stream *m_stream = nullptr; + std::vector m_audio_ring; + uint32_t m_audio_write = 0, m_audio_read = 0; + bool m_audio_primed = false; + uint32_t m_audio_prime_wait = 0; + + // Last time the audio path held anything at all to play, used to tell a + // real buffer underflow from the gaps between the driver's transfers. + uint32_t m_fma_last_audio_dclk = 0; +}; + +DECLARE_DEVICE_TYPE(CDI_DVC, cdidvc_device) + +#endif // MAME_MACHINE_CDIDVC_H diff --git a/src/mame/machine/mpeg1audio.cpp b/src/mame/machine/mpeg1audio.cpp new file mode 100644 index 00000000..bd9a1d16 --- /dev/null +++ b/src/mame/machine/mpeg1audio.cpp @@ -0,0 +1,872 @@ +// license:BSD-3-Clause +// copyright-holders:vibecodekun +/****************************************************************************** + + ISO/IEC 11172-3 (MPEG-1) Layer I/II audio decoder + + See mpeg1audio.h for the interface. + + Portions of this file are derived from PL_MPEG by Dominic Szablewski + (https://github.com/phoboslab/pl_mpeg), whose MP2 decoder is in turn + based on kjmp2 by Martin J. Fiedler, used under the following license: + + The MIT License (MIT) + + Copyright (c) 2019 Dominic Szablewski + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. + +*******************************************************************************/ + +#include "mpeg1audio.h" + +#include +#include +#include + +namespace { + +constexpr int LAYER_III = 1; +constexpr int LAYER_II = 2; +constexpr int LAYER_I = 3; + +constexpr int MODE_STEREO = 0; +constexpr int MODE_JOINT_STEREO = 1; +constexpr int MODE_DUAL_CHANNEL = 2; +constexpr int MODE_MONO = 3; + +const uint16_t SAMPLE_RATE[8] = +{ + 44100, 48000, 32000, 0, // MPEG-1 + 22050, 24000, 16000, 0 // MPEG-2 +}; + +const uint16_t BIT_RATE_LAYER_II[28] = +{ + 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, // MPEG-1 + 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2 +}; + +const uint16_t BIT_RATE_LAYER_I[28] = +{ + 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, // MPEG-1 + 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 // MPEG-2 +}; + +// Scale factors are 2 * 2^(-index/3); the three bases cover the residue of the +// index modulo three, and the shift covers the rest. +const int32_t SCALEFACTOR_BASE[3] = { 0x02000000, 0x01965fea, 0x01428a30 }; + +//************************************************************************** +// Layer II bit allocation lookup (ISO tables 3-B.2a through 3-B.2d) +//************************************************************************** + +// Table selection is by bit rate per channel and then by sample rate. The +// encoded value carries the subband limit in its low six bits and the table +// row set above that. +constexpr uint8_t QUANT_TAB_A = 27 | 64; // 3-B.2a, high rate, sblimit 27 +constexpr uint8_t QUANT_TAB_B = 30 | 64; // 3-B.2b, high rate, sblimit 30 +constexpr uint8_t QUANT_TAB_C = 8; // 3-B.2c, low rate, sblimit 8 +constexpr uint8_t QUANT_TAB_D = 12; // 3-B.2d, low rate, sblimit 12 + +const uint8_t QUANT_LUT_STEP_1[2][16] = +{ + // 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 kbit/s + { 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, // mono + // the same, per channel, for two-channel modes + { 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2 } // stereo +}; + +const uint8_t QUANT_LUT_STEP_2[3][3] = +{ + // 44.1 kHz 48 kHz 32 kHz + { QUANT_TAB_C, QUANT_TAB_C, QUANT_TAB_D }, // 32 - 48 kbit/s/ch + { QUANT_TAB_A, QUANT_TAB_A, QUANT_TAB_A }, // 56 - 80 kbit/s/ch + { QUANT_TAB_B, QUANT_TAB_A, QUANT_TAB_B } // 96 + kbit/s/ch +}; + +// Per subband: number of allocation bits in the high nibble, the row of the +// step 4 table in the low nibble. +const uint8_t QUANT_LUT_STEP_3[3][32] = +{ + // low rate (3-B.2c and 3-B.2d) + { + 0x44, 0x44, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34 + }, + // high rate (3-B.2a and 3-B.2b) + { + 0x43, 0x43, 0x43, + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 + }, + // MPEG-2 low sample rate (ISO 13818-3 B.2) + { + 0x45, 0x45, 0x45, 0x45, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24 + } +}; + +// Row of the step 3 table plus the allocation code gives a quantiser index. +const uint8_t QUANT_LUT_STEP_4[6][16] = +{ + { 0, 1, 2, 17 }, + { 0, 1, 2, 3, 4, 5, 6, 17 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17 }, + { 0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }, + { 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } +}; + +} // anonymous namespace + +// Quantiser specifications, indexed by the value the tables above produce. +// Index zero means the subband carries no data. +const mpeg1_detail::quantizer_spec QUANT_TAB[17] = +{ + { 3, 1, 5 }, { 5, 1, 7 }, { 7, 0, 3 }, { 9, 1, 10 }, + { 15, 0, 4 }, { 31, 0, 5 }, { 63, 0, 6 }, { 127, 0, 7 }, + { 255, 0, 8 }, { 511, 0, 9 }, { 1023, 0, 10 }, { 2047, 0, 11 }, + { 4095, 0, 12 }, { 8191, 0, 13 }, { 16383, 0, 14 }, { 32767, 0, 15 }, + { 65535, 0, 16 } +}; + +const mpeg1_detail::quantizer_spec QUANT_TAB_LAYER_I[14] = +{ + { 3, 0, 2 }, { 7, 0, 3 }, { 15, 0, 4 }, { 31, 0, 5 }, + { 63, 0, 6 }, { 127, 0, 7 }, { 255, 0, 8 }, { 511, 0, 9 }, + { 1023, 0, 10 }, { 2047, 0, 11 }, { 4095, 0, 12 }, { 8191, 0, 13 }, + { 16383, 0, 14 }, { 32767, 0, 15 } +}; + +namespace { + +// ISO/IEC 11172-3 Table B.3, the polyphase synthesis window D[i], +// scaled by 65536 so that every entry is an exact integer. +const int32_t SYNTHESIS_WINDOW[512] = +{ + 0, -1, -1, -1, -1, -1, -1, -2, + -2, -2, -2, -3, -3, -4, -4, -5, + -5, -6, -7, -7, -8, -9, -10, -11, + -13, -14, -16, -17, -19, -21, -24, -26, + -29, -31, -35, -38, -41, -45, -49, -53, + -58, -63, -68, -73, -79, -85, -91, -97, + -104, -111, -117, -125, -132, -139, -147, -154, + -161, -169, -176, -183, -190, -196, -202, -208, + 213, 218, 222, 225, 227, 228, 228, 227, + 224, 221, 215, 208, 200, 189, 177, 163, + 146, 127, 106, 83, 57, 29, -2, -36, + -72, -111, -153, -197, -244, -294, -347, -401, + -459, -519, -581, -645, -711, -779, -848, -919, + -991, -1064, -1137, -1210, -1283, -1356, -1428, -1498, + -1567, -1634, -1698, -1759, -1817, -1870, -1919, -1962, + -2001, -2032, -2057, -2075, -2085, -2087, -2080, -2063, + 2037, 2000, 1952, 1893, 1822, 1739, 1644, 1535, + 1414, 1280, 1131, 970, 794, 605, 402, 185, + -45, -288, -545, -814, -1095, -1388, -1692, -2006, + -2330, -2663, -3004, -3351, -3705, -4063, -4425, -4788, + -5153, -5517, -5879, -6237, -6589, -6935, -7271, -7597, + -7910, -8209, -8491, -8755, -8998, -9219, -9416, -9585, + -9727, -9838, -9916, -9959, -9966, -9935, -9863, -9750, + -9592, -9389, -9139, -8840, -8492, -8092, -7640, -7134, + 6574, 5959, 5288, 4561, 3776, 2935, 2037, 1082, + 70, -998, -2122, -3300, -4533, -5818, -7154, -8540, + -9975, -11455, -12980, -14548, -16155, -17799, -19478, -21189, + -22929, -24694, -26482, -28289, -30112, -31947, -33791, -35640, + -37489, -39336, -41176, -43006, -44821, -46617, -48390, -50137, + -51853, -53534, -55178, -56778, -58333, -59838, -61289, -62684, + -64019, -65290, -66494, -67629, -68692, -69679, -70590, -71420, + -72169, -72835, -73415, -73908, -74313, -74630, -74856, -74992, + 75038, 74992, 74856, 74630, 74313, 73908, 73415, 72835, + 72169, 71420, 70590, 69679, 68692, 67629, 66494, 65290, + 64019, 62684, 61289, 59838, 58333, 56778, 55178, 53534, + 51853, 50137, 48390, 46617, 44821, 43006, 41176, 39336, + 37489, 35640, 33791, 31947, 30112, 28289, 26482, 24694, + 22929, 21189, 19478, 17799, 16155, 14548, 12980, 11455, + 9975, 8540, 7154, 5818, 4533, 3300, 2122, 998, + -70, -1082, -2037, -2935, -3776, -4561, -5288, -5959, + 6574, 7134, 7640, 8092, 8492, 8840, 9139, 9389, + 9592, 9750, 9863, 9935, 9966, 9959, 9916, 9838, + 9727, 9585, 9416, 9219, 8998, 8755, 8491, 8209, + 7910, 7597, 7271, 6935, 6589, 6237, 5879, 5517, + 5153, 4788, 4425, 4063, 3705, 3351, 3004, 2663, + 2330, 2006, 1692, 1388, 1095, 814, 545, 288, + 45, -185, -402, -605, -794, -970, -1131, -1280, + -1414, -1535, -1644, -1739, -1822, -1893, -1952, -2000, + 2037, 2063, 2080, 2087, 2085, 2075, 2057, 2032, + 2001, 1962, 1919, 1870, 1817, 1759, 1698, 1634, + 1567, 1498, 1428, 1356, 1283, 1210, 1137, 1064, + 991, 919, 848, 779, 711, 645, 581, 519, + 459, 401, 347, 294, 244, 197, 153, 111, + 72, 36, 2, -29, -57, -83, -106, -127, + -146, -163, -177, -189, -200, -208, -215, -221, + -224, -227, -228, -228, -227, -225, -222, -218, + 213, 208, 202, 196, 190, 183, 176, 169, + 161, 154, 147, 139, 132, 125, 117, 111, + 104, 97, 91, 85, 79, 73, 68, 63, + 58, 53, 49, 45, 41, 38, 35, 31, + 29, 26, 24, 21, 19, 17, 16, 14, + 13, 11, 10, 9, 8, 7, 7, 6, + 5, 5, 4, 4, 3, 3, 2, 2, + 2, 2, 1, 1, 1, 1, 1, 1, +}; + +// Matrixing coefficients N[i][k] = cos((16 + i) * (2k + 1) * pi / 64), +// built once on first use. +struct matrix_table +{ + double n[64][32]; + + matrix_table() + { + for (int i = 0; i < 64; i++) + for (int k = 0; k < 32; k++) + n[i][k] = std::cos((16 + i) * (2 * k + 1) * 3.14159265358979323846 / 64.0); + } +}; + +const matrix_table &matrix() +{ + static const matrix_table table; + return table; +} + +inline int16_t clamp_sample(double value) +{ + const int v = int(value); + return int16_t((v < -32768) ? -32768 : ((v > 32767) ? 32767 : v)); +} + +} // anonymous namespace + +//************************************************************************** +// Construction +//************************************************************************** + +mpeg1_audio_decoder::mpeg1_audio_decoder() +{ + reset(); +} + +void mpeg1_audio_decoder::reset() +{ + m_buffer.clear(); + m_read_pos = 0; + m_bit_pos = 0; + m_underflow = false; + m_frame_decoded = false; + + m_frame_header = 0; + m_version = 0; + m_layer = 0; + m_bitrate_index = 0; + m_samplerate_index = 3; + m_mode = 0; + m_bound = 0; + m_sample_rate = 0; + m_bit_rate = 0; + m_channels = 2; + m_samples_per_frame = 1152; + m_next_frame_size = 0; + m_has_header = false; + + std::memset(m_allocation, 0, sizeof(m_allocation)); + std::memset(m_scale_factor_info, 0, sizeof(m_scale_factor_info)); + std::memset(m_scale_factor, 0, sizeof(m_scale_factor)); + std::memset(m_sample, 0, sizeof(m_sample)); + std::memset(m_v, 0, sizeof(m_v)); + m_v_pos = 0; + + m_output.assign(1152 * 2, 0); + m_sample_count = 0; +} + +void mpeg1_audio_decoder::get_decoder_state(decoder_state &state) const +{ + // Rebase so the read position becomes byte 0; nothing behind it is read + // again. Only a driver that ignored the FMA status entirely could push the + // buffer past the cap, and the clamp keeps that from overrunning the copy. + const size_t available = m_buffer.size() - m_read_pos; + const size_t size = std::min(available, decoder_state::max_buffer); + state.buffer_size = uint32_t(size); + state.bit_pos = uint32_t(std::min(m_bit_pos - m_read_pos * 8, size * 8)); + if (size != 0) + std::memcpy(state.buffer, m_buffer.data() + m_read_pos, size); + + state.underflow = m_underflow ? 1 : 0; + state.frame_decoded = m_frame_decoded ? 1 : 0; + state.has_header = m_has_header ? 1 : 0; + + state.frame_header = m_frame_header; + state.version = m_version; + state.layer = m_layer; + state.bitrate_index = m_bitrate_index; + state.samplerate_index = m_samplerate_index; + state.mode = m_mode; + state.bound = m_bound; + state.sample_rate = m_sample_rate; + state.bit_rate = m_bit_rate; + state.channels = m_channels; + state.samples_per_frame = m_samples_per_frame; + state.next_frame_size = m_next_frame_size; + + std::memcpy(state.v, m_v, sizeof(state.v)); + state.v_pos = m_v_pos; +} + +void mpeg1_audio_decoder::set_decoder_state(const decoder_state &state) +{ + const size_t size = std::min(state.buffer_size, decoder_state::max_buffer); + m_buffer.assign(state.buffer, state.buffer + size); + m_read_pos = 0; + m_bit_pos = std::min(state.bit_pos, size * 8); + + m_underflow = state.underflow != 0; + m_frame_decoded = state.frame_decoded != 0; + m_has_header = state.has_header != 0; + + m_frame_header = state.frame_header; + m_version = state.version; + m_layer = state.layer; + m_bitrate_index = state.bitrate_index; + m_samplerate_index = state.samplerate_index; + m_mode = state.mode; + m_bound = state.bound; + m_sample_rate = state.sample_rate; + m_bit_rate = state.bit_rate; + m_channels = state.channels; + m_samples_per_frame = state.samples_per_frame; + m_next_frame_size = state.next_frame_size; + + std::memcpy(m_v, state.v, sizeof(m_v)); + m_v_pos = state.v_pos; + + // Per-frame working state is rebuilt by the next decode(); clear the + // pointer table so a stale entry can never be followed if that assumption + // is ever broken. + std::memset(m_allocation, 0, sizeof(m_allocation)); + m_sample_count = 0; +} + +//************************************************************************** +// Bit reading +//************************************************************************** + +void mpeg1_audio_decoder::write(const uint8_t *data, size_t length) +{ + if (m_buffer.size() - m_read_pos + length > m_buffer_limit) + { + const size_t drop = std::min(m_read_pos, m_buffer.size()); + if (drop) + { + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + drop); + m_bit_pos -= drop * 8; + m_read_pos = 0; + } + } + + m_buffer.insert(m_buffer.end(), data, data + length); +} + +bool mpeg1_audio_decoder::have_bits(size_t count) const +{ + return m_bit_pos + count <= m_buffer.size() * 8; +} + +uint32_t mpeg1_audio_decoder::read_bits(int count) +{ + uint32_t value = 0; + while (count--) + { + if (m_bit_pos >= m_buffer.size() * 8) + { + value <<= 1; + m_bit_pos++; + continue; + } + value = (value << 1) | ((m_buffer[m_bit_pos >> 3] >> (7 - (m_bit_pos & 7))) & 1); + m_bit_pos++; + } + return value; +} + +void mpeg1_audio_decoder::align_to_byte() +{ + m_bit_pos = (m_bit_pos + 7) & ~size_t(7); +} + +//************************************************************************** +// Frame header +//************************************************************************** + +bool mpeg1_audio_decoder::find_frame_sync() +{ + // A frame begins with eleven set bits; scan byte-aligned since that is + // where a valid frame always starts. + for (size_t i = m_bit_pos >> 3; i + 1 < m_buffer.size(); i++) + { + if (m_buffer[i] == 0xff && (m_buffer[i + 1] & 0xe0) == 0xe0) + { + m_bit_pos = i * 8; + return true; + } + } + + // Nothing found; drop what has been scanned so the buffer does not grow. + m_bit_pos = (m_buffer.size() > 1) ? (m_buffer.size() - 1) * 8 : 0; + return false; +} + +bool mpeg1_audio_decoder::decode_header() +{ + if (!have_bits(32)) + return false; + + const size_t start = m_bit_pos; + const uint32_t header = read_bits(32); + + if ((header & 0xffe00000) != 0xffe00000) + { + m_bit_pos = start + 8; + return false; + } + + const int version = (header >> 19) & 3; + const int layer = (header >> 17) & 3; + const int bitrate_index = (header >> 12) & 0x0f; + const int samplerate_index = (header >> 10) & 3; + const int padding = (header >> 9) & 1; + const int mode = (header >> 6) & 3; + + // Only MPEG-1 (version 3) and MPEG-2 (version 2) Layer I/II are handled. + if (version == 1 || layer == 0 || layer == LAYER_III || + bitrate_index == 0 || bitrate_index == 0x0f || samplerate_index == 3) + { + m_bit_pos = start + 8; + return false; + } + + m_frame_header = header; + m_version = version; + m_layer = layer; + m_bitrate_index = bitrate_index - 1; + m_samplerate_index = samplerate_index; + m_mode = mode; + + const bool mpeg2 = (version == 2); + m_sample_rate = SAMPLE_RATE[samplerate_index + (mpeg2 ? 4 : 0)]; + m_bit_rate = (layer == LAYER_I) + ? BIT_RATE_LAYER_I[m_bitrate_index + (mpeg2 ? 14 : 0)] + : BIT_RATE_LAYER_II[m_bitrate_index + (mpeg2 ? 14 : 0)]; + + m_channels = (mode == MODE_MONO) ? 1 : 2; + + // In joint stereo the mode extension gives the number of subbands that are + // still coded separately. + m_bound = (mode == MODE_JOINT_STEREO) ? ((((header >> 4) & 3) + 1) << 2) : 32; + if (mode == MODE_MONO) + m_bound = 0; + + if (layer == LAYER_I) + { + m_samples_per_frame = 384; + m_next_frame_size = (12000 * m_bit_rate / m_sample_rate + padding) * 4; + } + else + { + m_samples_per_frame = 1152; + m_next_frame_size = 144000 * m_bit_rate / m_sample_rate + padding; + } + + m_has_header = true; + return true; +} + +//************************************************************************** +// Frame decoding +//************************************************************************** + +int mpeg1_audio_decoder::decode() +{ + m_sample_count = 0; + + for (;;) + { + const size_t frame_start = m_bit_pos; + + if (!find_frame_sync()) + { + m_underflow = true; + return 0; + } + + if (!decode_header()) + { + if (m_bit_pos <= frame_start) + return 0; + continue; + } + + // The whole frame has to be present before it can be decoded. + const size_t frame_bits = size_t(m_next_frame_size) * 8; + const size_t header_start = m_bit_pos - 32; + if (header_start + frame_bits > m_buffer.size() * 8) + { + m_bit_pos = header_start; + m_underflow = true; + return 0; + } + + m_underflow = false; + + // Skip the CRC when protection is enabled. + if (((m_frame_header >> 16) & 1) == 0) + read_bits(16); + + if (m_layer == LAYER_I) + decode_layer_i(); + else + decode_layer_ii(); + + // Move to the start of the next frame regardless of how many bits the + // payload actually used. + m_bit_pos = header_start + frame_bits; + m_read_pos = m_bit_pos >> 3; + + if (m_read_pos >= 16384) + { + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_read_pos); + m_bit_pos -= m_read_pos * 8; + m_read_pos = 0; + } + + m_frame_decoded = true; + return m_sample_count; + } +} + +const mpeg1_audio_decoder::quantizer_spec *mpeg1_audio_decoder::read_allocation(int sb, int table) +{ + const int step3 = QUANT_LUT_STEP_3[table][sb]; + const int bits = step3 >> 4; + const int row = step3 & 15; + + const int code = int(read_bits(bits)); + const int index = QUANT_LUT_STEP_4[row][code]; + + return index ? &QUANT_TAB[index - 1] : nullptr; +} + +void mpeg1_audio_decoder::read_samples(int channel, int sb, int part) +{ + const quantizer_spec *const q = m_allocation[channel][sb]; + double *const out = m_sample[channel][sb]; + + if (!q) + { + out[0] = out[1] = out[2] = 0.0; + return; + } + + // Resolve the scale factor: 2 * 2^(-index/3), with 63 meaning silence. + const int sf_index = m_scale_factor[channel][sb][part]; + double scale; + if (sf_index == 63) + { + scale = 0.0; + } + else + { + const int shift = sf_index / 3; + scale = double(SCALEFACTOR_BASE[sf_index % 3]) / double(1 << 24) / double(1 << shift); + } + + int code[3]; + if (q->group) + { + // Three samples share one codeword. + uint32_t value = read_bits(q->bits); + code[0] = int(value % q->levels); + value /= q->levels; + code[1] = int(value % q->levels); + code[2] = int(value / q->levels); + } + else + { + code[0] = int(read_bits(q->bits)); + code[1] = int(read_bits(q->bits)); + code[2] = int(read_bits(q->bits)); + } + + // The code is a two's complement fraction with its most significant bit + // inverted, so subtracting from the midpoint recovers the signed value. + const int adjust = ((q->levels + 1) >> 1) - 1; + const double norm = 1.0 / double(q->levels + 1); + + for (int i = 0; i < 3; i++) + out[i] = double(adjust - code[i]) * norm * scale; +} + +void mpeg1_audio_decoder::decode_layer_ii() +{ + const int table1 = (m_mode == MODE_MONO) ? 0 : 1; + const int table2 = QUANT_LUT_STEP_1[table1][m_bitrate_index]; + const int encoded = QUANT_LUT_STEP_2[table2][m_samplerate_index]; + + int sblimit = encoded & 63; + const int table = encoded >> 6; + + int bound = (m_mode == MODE_JOINT_STEREO) ? m_bound : ((m_mode == MODE_MONO) ? 0 : 32); + if (bound > sblimit) + bound = sblimit; + + // Bit allocation + std::memset(m_allocation, 0, sizeof(m_allocation)); + for (int sb = 0; sb < bound; sb++) + { + m_allocation[0][sb] = read_allocation(sb, table); + m_allocation[1][sb] = read_allocation(sb, table); + } + for (int sb = bound; sb < sblimit; sb++) + m_allocation[0][sb] = m_allocation[1][sb] = read_allocation(sb, table); + + // Scale factor selection information + for (int sb = 0; sb < sblimit; sb++) + { + for (int ch = 0; ch < m_channels; ch++) + { + if (m_allocation[ch][sb]) + m_scale_factor_info[ch][sb] = uint8_t(read_bits(2)); + } + if (m_mode == MODE_MONO) + m_scale_factor_info[1][sb] = m_scale_factor_info[0][sb]; + } + + // Scale factors + for (int sb = 0; sb < sblimit; sb++) + { + for (int ch = 0; ch < m_channels; ch++) + { + if (!m_allocation[ch][sb]) + continue; + + int *const sf = m_scale_factor[ch][sb]; + switch (m_scale_factor_info[ch][sb]) + { + case 0: + sf[0] = int(read_bits(6)); + sf[1] = int(read_bits(6)); + sf[2] = int(read_bits(6)); + break; + case 1: + sf[0] = sf[1] = int(read_bits(6)); + sf[2] = int(read_bits(6)); + break; + case 2: + sf[0] = sf[1] = sf[2] = int(read_bits(6)); + break; + default: + sf[0] = int(read_bits(6)); + sf[1] = sf[2] = int(read_bits(6)); + break; + } + } + + if (m_mode == MODE_MONO) + { + m_scale_factor[1][sb][0] = m_scale_factor[0][sb][0]; + m_scale_factor[1][sb][1] = m_scale_factor[0][sb][1]; + m_scale_factor[1][sb][2] = m_scale_factor[0][sb][2]; + } + } + + // Twelve granules, each producing three sets of 32 subband samples. + int out_pos = 0; + for (int granule = 0; granule < 12; granule++) + { + const int part = granule >> 2; + + for (int sb = 0; sb < bound; sb++) + { + read_samples(0, sb, part); + read_samples(1, sb, part); + } + + for (int sb = bound; sb < sblimit; sb++) + { + read_samples(0, sb, part); + // Above the bound both channels share the coded samples. + for (int i = 0; i < 3; i++) + m_sample[1][sb][i] = m_sample[0][sb][i]; + } + + for (int sb = sblimit; sb < 32; sb++) + { + for (int ch = 0; ch < 2; ch++) + m_sample[ch][sb][0] = m_sample[ch][sb][1] = m_sample[ch][sb][2] = 0.0; + } + + for (int part_index = 0; part_index < 3; part_index++) + synthesise(part_index, out_pos); + } + + m_sample_count = out_pos / 2; +} + +void mpeg1_audio_decoder::decode_layer_i() +{ + int bound = (m_mode == MODE_JOINT_STEREO) ? m_bound : ((m_mode == MODE_MONO) ? 0 : 32); + + std::memset(m_allocation, 0, sizeof(m_allocation)); + + for (int sb = 0; sb < bound; sb++) + { + for (int ch = 0; ch < 2; ch++) + { + const int code = int(read_bits(4)); + m_allocation[ch][sb] = code ? &QUANT_TAB_LAYER_I[code - 1] : nullptr; + } + } + for (int sb = bound; sb < 32; sb++) + { + const int code = int(read_bits(4)); + m_allocation[0][sb] = m_allocation[1][sb] = code ? &QUANT_TAB_LAYER_I[code - 1] : nullptr; + } + + for (int sb = 0; sb < 32; sb++) + { + for (int ch = 0; ch < m_channels; ch++) + { + if (m_allocation[ch][sb]) + m_scale_factor[ch][sb][0] = int(read_bits(6)); + } + if (m_mode == MODE_MONO) + m_scale_factor[1][sb][0] = m_scale_factor[0][sb][0]; + } + + int out_pos = 0; + for (int granule = 0; granule < 12; granule++) + { + for (int sb = 0; sb < bound; sb++) + { + for (int ch = 0; ch < 2; ch++) + { + const quantizer_spec *const q = m_allocation[ch][sb]; + if (!q) + { + m_sample[ch][sb][0] = 0.0; + continue; + } + + const int sf_index = m_scale_factor[ch][sb][0]; + const int shift = sf_index / 3; + const double scale = (sf_index == 63) ? 0.0 + : double(SCALEFACTOR_BASE[sf_index % 3]) / double(1 << 24) / double(1 << shift); + + const int code = int(read_bits(q->bits)); + const int adjust = ((q->levels + 1) >> 1) - 1; + m_sample[ch][sb][0] = double(adjust - code) / double(q->levels + 1) * scale; + } + } + + for (int sb = bound; sb < 32; sb++) + { + const quantizer_spec *const q = m_allocation[0][sb]; + if (!q) + { + m_sample[0][sb][0] = m_sample[1][sb][0] = 0.0; + continue; + } + + const int sf_index = m_scale_factor[0][sb][0]; + const int shift = sf_index / 3; + const double scale = (sf_index == 63) ? 0.0 + : double(SCALEFACTOR_BASE[sf_index % 3]) / double(1 << 24) / double(1 << shift); + + const int code = int(read_bits(q->bits)); + const int adjust = ((q->levels + 1) >> 1) - 1; + m_sample[0][sb][0] = m_sample[1][sb][0] = double(adjust - code) / double(q->levels + 1) * scale; + } + + synthesise(0, out_pos); + } + + m_sample_count = out_pos / 2; +} + +//************************************************************************** +// Polyphase synthesis filter (ISO/IEC 11172-3 clause 2.4.3.2) +//************************************************************************** + +void mpeg1_audio_decoder::synthesise(int part, int &out_pos) +{ + m_v_pos = (m_v_pos - 64) & 1023; + + const matrix_table &n = matrix(); + + for (int ch = 0; ch < 2; ch++) + { + // Matrixing: 32 subband samples become 64 values in the history. + double *const v = &m_v[ch][0]; + for (int i = 0; i < 64; i++) + { + double sum = 0.0; + for (int k = 0; k < 32; k++) + sum += n.n[i][k] * m_sample[ch][k][part]; + v[(m_v_pos + i) & 1023] = sum; + } + + // Build the 512-entry window input and apply the window. + double u[512]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 32; j++) + { + u[i * 64 + j] = v[(m_v_pos + i * 128 + j) & 1023]; + u[i * 64 + 32 + j] = v[(m_v_pos + i * 128 + 96 + j) & 1023]; + } + } + + // The window table is stored scaled by 65536. + double out[32]; + for (int j = 0; j < 32; j++) + { + double sum = 0.0; + for (int i = 0; i < 16; i++) + sum += u[j + 32 * i] * (double(SYNTHESIS_WINDOW[j + 32 * i]) / 65536.0); + out[j] = sum; + } + + for (int j = 0; j < 32; j++) + m_output[out_pos + j * 2 + ch] = clamp_sample(out[j] * 32767.0); + } + + // A mono stream fills only channel zero, so mirror it. + if (m_mode == MODE_MONO) + { + for (int j = 0; j < 32; j++) + m_output[out_pos + j * 2 + 1] = m_output[out_pos + j * 2]; + } + + out_pos += 64; +} diff --git a/src/mame/machine/mpeg1audio.h b/src/mame/machine/mpeg1audio.h new file mode 100644 index 00000000..0f94a227 --- /dev/null +++ b/src/mame/machine/mpeg1audio.h @@ -0,0 +1,177 @@ +// license:BSD-3-Clause +// copyright-holders:vibecodekun +/****************************************************************************** + + ISO/IEC 11172-3 (MPEG-1) Layer I/II audio decoder + + Companion to mpeg1video.h for the CD-i Digital Video Cartridge, and like it + deliberately independent of MAME so it can be tested on its own. The + quantiser tables follow pl_mpeg and kjmp2; see mpeg1audio.cpp for pl_mpeg's + MIT license notice. + + Green Book streams are Layer II, 44.1 kHz, 224 kbit/s stereo, but Layer I + and the other sample rates are handled as well since they cost almost + nothing to support. + +*******************************************************************************/ + +#ifndef MAME_MACHINE_MPEG1AUDIO_H +#define MAME_MACHINE_MPEG1AUDIO_H + +#pragma once + +#include +#include +#include + +namespace mpeg1_detail { + +// One entry of the quantiser tables: how many levels the subband is coded +// with, whether three samples share a codeword, and the codeword width. +struct quantizer_spec +{ + uint16_t levels; + uint8_t group; + uint8_t bits; +}; + +} // namespace mpeg1_detail + +class mpeg1_audio_decoder +{ +public: + // Layer II carries 1152 samples per channel per frame, which is the most + // any one call to decode() can produce. + static constexpr int max_samples_per_frame = 1152; + + mpeg1_audio_decoder(); + + void reset(); + + // Feed demultiplexed elementary stream bytes. + void write(const uint8_t *data, size_t length); + + // Decode one frame. Returns the number of sample pairs produced, or zero + // if more data is needed. + int decode(); + + // Interleaved stereo output of the last decoded frame. Mono streams are + // duplicated to both channels so the consumer always sees stereo. + const int16_t *samples() const { return m_output.data(); } + int sample_count() const { return m_sample_count; } + + int sample_rate() const { return m_sample_rate; } + int channels() const { return m_channels; } + int bit_rate() const { return m_bit_rate; } + + // The 32 bits of the most recent frame header, which the cartridge exposes + // to the driver verbatim. + uint32_t frame_header() const { return m_frame_header; } + + // Set when a frame has been decoded since the last call to this. + bool take_frame_decoded() { const bool v = m_frame_decoded; m_frame_decoded = false; return v; } + + size_t buffered_bytes() const { return m_buffer.size() - m_read_pos; } + void set_buffer_limit(size_t bytes) { m_buffer_limit = bytes; } + bool underflowed() const { return m_underflow; } + void clear_underflow() { m_underflow = false; } + + // Everything that has to survive a save state. + // + // The per-frame working set - the allocation table, scale factors, the + // dequantised samples and the output buffer - is deliberately absent. + // decode() fills and consumes all of it within the one call, and the + // allocation table is an array of pointers into static tables that would + // have to be turned back into indices for no benefit. + // + // What does have to come back is the polyphase synthesis history. It is a + // rolling window that spans frames, and starting it from zero puts a click + // on the first frame after every load. + struct decoder_state + { + // write() compacts but does not refuse data once the limit is reached, + // so unlike the video decoder the buffer can run past its limit if the + // driver keeps pushing. Four times the cartridge's 64KB gives that + // plenty of room; a buffer somehow larger still is truncated, costing + // the tail of the stream and resyncing at the next frame header. + static constexpr size_t max_buffer = 256 * 1024; + + uint32_t buffer_size = 0; + uint32_t bit_pos = 0; // bit offset into buffer[] + uint8_t buffer[max_buffer] = { 0 }; + + uint8_t underflow = 0; + uint8_t frame_decoded = 0; + uint8_t has_header = 0; + + uint32_t frame_header = 0; + int32_t version = 0; + int32_t layer = 0; + int32_t bitrate_index = 0; + int32_t samplerate_index = 3; + int32_t mode = 0; + int32_t bound = 0; + int32_t sample_rate = 0; + int32_t bit_rate = 0; + int32_t channels = 2; + int32_t samples_per_frame = 1152; + int32_t next_frame_size = 0; + + double v[2][1024] = { { 0.0 } }; + int32_t v_pos = 0; + }; + + void get_decoder_state(decoder_state &state) const; + void set_decoder_state(const decoder_state &state); + +private: + using quantizer_spec = mpeg1_detail::quantizer_spec; + + bool have_bits(size_t count) const; + uint32_t read_bits(int count); + void align_to_byte(); + + bool find_frame_sync(); + bool decode_header(); + void decode_layer_ii(); + void decode_layer_i(); + const quantizer_spec *read_allocation(int sb, int table); + void read_samples(int channel, int sb, int part); + void synthesise(int part, int &out_pos); + + std::vector m_buffer; + size_t m_read_pos = 0; + size_t m_bit_pos = 0; + size_t m_buffer_limit = 128 * 1024; + bool m_underflow = false; + bool m_frame_decoded = false; + + // header fields + uint32_t m_frame_header = 0; + int m_version = 0; + int m_layer = 0; + int m_bitrate_index = 0; + int m_samplerate_index = 3; + int m_mode = 0; + int m_bound = 0; + int m_sample_rate = 0; + int m_bit_rate = 0; + int m_channels = 2; + int m_samples_per_frame = 1152; + int m_next_frame_size = 0; + bool m_has_header = false; + + const quantizer_spec *m_allocation[2][32]; + uint8_t m_scale_factor_info[2][32]; + int m_scale_factor[2][32][3]; + double m_sample[2][32][3]; + + // polyphase synthesis history + double m_v[2][1024]; + int m_v_pos = 0; + + std::vector m_output; + int m_sample_count = 0; +}; + +#endif // MAME_MACHINE_MPEG1AUDIO_H diff --git a/src/mame/machine/mpeg1demux.cpp b/src/mame/machine/mpeg1demux.cpp new file mode 100644 index 00000000..18d657bd --- /dev/null +++ b/src/mame/machine/mpeg1demux.cpp @@ -0,0 +1,297 @@ +// license:GPL-3.0-only +// copyright-holders:Andre Zeps, vibecodekun +/****************************************************************************** + + ISO/IEC 11172-1 (MPEG-1) program stream demultiplexer + + See mpeg1demux.h. This is a direct port of mpeg_demuxer.sv from the + CDi_MiSTer VMPEG core, including its habit of parking the state machine in + MAGIC_MATCH when a stream number does not match. + +*******************************************************************************/ + +#include "mpeg1demux.h" + +void mpeg1_demuxer::reset() +{ + m_state = IDLE; + m_packet_body = false; + m_length_decreasing = false; + m_packet_length = 0; + m_dts_present = false; + m_scr = m_scr_temp = 0; + m_pts = m_pts_temp = 0; + m_dts = m_dts_temp = 0; + m_scr_updated = m_pts_updated = m_dts_updated = false; + m_program_end = false; +} + +void mpeg1_demuxer::get_state(demux_state &state) const +{ + state.state = m_state; + state.packet_body = m_packet_body ? 1 : 0; + state.length_decreasing = m_length_decreasing ? 1 : 0; + state.dts_present = m_dts_present ? 1 : 0; + state.packet_length = m_packet_length; + state.scr = m_scr; + state.scr_temp = m_scr_temp; + state.pts = m_pts; + state.pts_temp = m_pts_temp; + state.dts = m_dts; + state.dts_temp = m_dts_temp; + state.scr_updated = m_scr_updated ? 1 : 0; + state.pts_updated = m_pts_updated ? 1 : 0; + state.dts_updated = m_dts_updated ? 1 : 0; + state.program_end = m_program_end ? 1 : 0; +} + +void mpeg1_demuxer::set_state(const demux_state &state) +{ + m_state = state.state; + m_packet_body = state.packet_body != 0; + m_length_decreasing = state.length_decreasing != 0; + m_dts_present = state.dts_present != 0; + m_packet_length = state.packet_length; + m_scr = state.scr; + m_scr_temp = state.scr_temp; + m_pts = state.pts; + m_pts_temp = state.pts_temp; + m_dts = state.dts; + m_dts_temp = state.dts_temp; + m_scr_updated = state.scr_updated != 0; + m_pts_updated = state.pts_updated != 0; + m_dts_updated = state.dts_updated != 0; + m_program_end = state.program_end != 0; +} + +// Helper for the 33-bit timestamp fields, which arrive as marker-separated +// 3/15/15 bit groups spread over five bytes. +static inline void put_bits(int64_t &value, int hi, int lo, uint8_t bits) +{ + const int64_t mask = ((int64_t(1) << (hi - lo + 1)) - 1) << lo; + value = (value & ~mask) | ((int64_t(bits) << lo) & mask); +} + +bool mpeg1_demuxer::feed(uint8_t data, uint8_t stream_filter) +{ + // The decoders are gated by the packet-body flag as it stands *before* + // this byte is processed, matching the registered output in the RTL. + const bool body = m_packet_body; + + m_scr_updated = m_pts_updated = m_dts_updated = m_program_end = false; + + if (m_length_decreasing) + { + if (m_packet_length == 1) + { + m_length_decreasing = false; + m_packet_body = false; + } + m_packet_length--; + } + + switch (m_state) + { + case PACK5: + m_state = IDLE; + m_scr = m_scr_temp; + m_scr_updated = true; + break; + + case PACK4: m_state = PACK5; put_bits(m_scr_temp, 6, 0, (data >> 1) & 0x7f); break; + case PACK3: m_state = PACK4; put_bits(m_scr_temp, 14, 7, data); break; + case PACK2: m_state = PACK3; put_bits(m_scr_temp, 21, 15, (data >> 1) & 0x7f); break; + case PACK1: m_state = PACK2; put_bits(m_scr_temp, 29, 22, data); break; + case PACK0: m_state = PACK1; put_bits(m_scr_temp, 32, 30, (data >> 1) & 0x07); break; + + case PES8: + // Only reached when a PTS was present; a DTS is optional but can + // never occur without one. + m_state = IDLE; + m_pts = m_pts_temp; + m_pts_updated = true; + // Without a DTS the VMPEG uses the PTS in its place. + m_dts = m_dts_present ? m_dts_temp : m_pts_temp; + m_dts_updated = true; + break; + + case PES_DTS4: + if ((data & 1)) + { + put_bits(m_dts_temp, 6, 0, (data >> 1) & 0x7f); + m_packet_body = true; + m_state = PES8; + } + else + { + m_state = IDLE; + } + break; + + case PES_DTS3: m_state = PES_DTS4; put_bits(m_dts_temp, 14, 7, data); break; + + case PES_DTS2: + if ((data & 1)) + { + put_bits(m_dts_temp, 21, 15, (data >> 1) & 0x7f); + m_state = PES_DTS3; + } + else + { + m_state = IDLE; + } + break; + + case PES_DTS1: m_state = PES_DTS2; put_bits(m_dts_temp, 29, 22, data); break; + + case PES_DTS0: + if ((data & 0xf1) == 0x11) + { + put_bits(m_dts_temp, 32, 30, (data >> 1) & 0x07); + m_state = PES_DTS1; + } + else + { + m_state = IDLE; + } + break; + + case PES7: + if ((data & 1)) + { + put_bits(m_pts_temp, 6, 0, (data >> 1) & 0x7f); + if (m_dts_present) + { + m_state = PES_DTS0; + } + else + { + m_packet_body = true; + m_state = PES8; + } + } + else + { + m_state = IDLE; + } + break; + + case PES6: m_state = PES7; put_bits(m_pts_temp, 14, 7, data); break; + + case PES5: + if ((data & 1)) + { + put_bits(m_pts_temp, 21, 15, (data >> 1) & 0x7f); + m_state = PES6; + } + else + { + m_state = IDLE; + } + break; + + case PES4: m_state = PES5; put_bits(m_pts_temp, 29, 22, data); break; + + case PES2: + if ((data & 0xf1) == 0x21) // PTS only + { + put_bits(m_pts_temp, 32, 30, (data >> 1) & 0x07); + m_dts_present = false; + m_state = PES4; + } + else if ((data & 0xf1) == 0x31) // PTS and DTS + { + put_bits(m_pts_temp, 32, 30, (data >> 1) & 0x07); + m_dts_present = true; + m_state = PES4; + } + else if (data == 0x0f) // neither + { + m_state = IDLE; + m_packet_body = true; + } + else if ((data & 0xc0) == 0x40) // STD buffer size, two bytes + { + m_state = PES3; + } + else if (data == 0xff) // stuffing + { + m_state = PES2; + } + else + { + m_state = IDLE; + } + break; + + case PES3: + // ignore the second STD buffer size byte + m_state = PES2; + break; + + case PES1: + m_state = PES2; + m_packet_length = (m_packet_length & 0xff00) | data; + m_length_decreasing = true; + break; + + case PES0: + m_state = PES1; + m_packet_length = (m_packet_length & 0x00ff) | (uint16_t(data) << 8); + break; + + case MAGIC_MATCH: + if (data == 0xba) + { + m_state = PACK0; + } + else if (data == 0xb9) + { + m_program_end = true; + m_state = IDLE; + } + else if ((data & 0xf0) == m_stream_class) + { + // An elementary stream of the class this demuxer is filtering: + // 0xCn for audio, 0xEn for video. The MiSTer RTL accepts both + // classes in both instances, which with the usual stream number + // of zero lets video packets fall into the audio path; each + // instance only looks at its own class here. + // + // On a stream number mismatch the state machine stays parked in + // MAGIC_MATCH rather than returning to IDLE, as in the RTL. + if ((data & 0x0f) == stream_filter) + m_state = PES0; + } + else + { + m_state = IDLE; + } + break; + + case MAGIC2: + if (data == 0x01) + m_state = MAGIC_MATCH; + else if (data == 0x00) + m_state = MAGIC2; + else + m_state = IDLE; + break; + + case MAGIC0: + m_state = (data == 0x00) ? MAGIC2 : IDLE; + break; + + case IDLE: + default: + // A start code prefix is only looked for outside of a packet body. + if (data == 0x00 && !m_packet_body) + m_state = MAGIC0; + else + m_state = IDLE; + break; + } + + return body; +} + diff --git a/src/mame/machine/mpeg1demux.h b/src/mame/machine/mpeg1demux.h new file mode 100644 index 00000000..76fe27d7 --- /dev/null +++ b/src/mame/machine/mpeg1demux.h @@ -0,0 +1,101 @@ +// license:GPL-3.0-only +// copyright-holders:Andre Zeps, vibecodekun +/****************************************************************************** + + ISO/IEC 11172-1 (MPEG-1) program stream demultiplexer + + A byte-at-a-time state machine, ported from the CDi_MiSTer VMPEG core by + Andre Zeps (https://github.com/MiSTer-devel/CDi_MiSTer, + rtl/mpeg/mpeg_demuxer.sv), which is licensed under the GNU General Public + License version 3. The Digital Video Cartridge runs one instance + for audio and one for video, each fed its own copy of the program stream by + the driver, and each picking out the elementary stream it cares about. + + Kept free of MAME dependencies so it can be tested against captured discs. + +*******************************************************************************/ + +#ifndef MAME_MACHINE_MPEG1DEMUX_H +#define MAME_MACHINE_MPEG1DEMUX_H + +#pragma once + +#include + +class mpeg1_demuxer +{ +public: + // stream_class is 0xc0 for audio or 0xe0 for video. + explicit mpeg1_demuxer(uint8_t stream_class = 0xe0) : m_stream_class(stream_class) { } + + void set_stream_class(uint8_t stream_class) { m_stream_class = stream_class; } + void reset(); + + // Feeds one byte. Returns true when that byte is part of the body of a + // packet belonging to the selected stream, i.e. elementary stream data. + bool feed(uint8_t data, uint8_t stream_filter); + + // Timestamps carried by the stream, in 90 kHz ticks. The *_updated flags + // refer to the most recent call to feed(). + int64_t system_clock_reference() const { return m_scr; } + int64_t presentation_timestamp() const { return m_pts; } + int64_t decoding_timestamp() const { return m_dts; } + + bool scr_updated() const { return m_scr_updated; } + bool pts_updated() const { return m_pts_updated; } + bool dts_updated() const { return m_dts_updated; } + bool program_end() const { return m_program_end; } + + // The whole of the state machine, as plain data so the owning device can + // save it. A demuxer restored mid-packet has to come back knowing how much + // of the packet body is still to come, or it will hand the rest of the + // packet to the decoder as though it were start codes. + // + // The stream class is configuration rather than state and is deliberately + // left out; the owner sets it when it constructs the demuxer. + struct demux_state + { + uint8_t state = IDLE; + uint8_t packet_body = 0; + uint8_t length_decreasing = 0; + uint8_t dts_present = 0; + uint16_t packet_length = 0; + int64_t scr = 0, scr_temp = 0; + int64_t pts = 0, pts_temp = 0; + int64_t dts = 0, dts_temp = 0; + uint8_t scr_updated = 0; + uint8_t pts_updated = 0; + uint8_t dts_updated = 0; + uint8_t program_end = 0; + }; + + void get_state(demux_state &state) const; + void set_state(const demux_state &state); + +private: + enum state : uint8_t + { + IDLE, MAGIC0, MAGIC2, MAGIC_MATCH, + PACK0, PACK1, PACK2, PACK3, PACK4, PACK5, + PES0, PES1, PES2, PES3, PES4, PES5, PES6, PES7, PES8, + PES_DTS0, PES_DTS1, PES_DTS2, PES_DTS3, PES_DTS4 + }; + + uint8_t m_stream_class; + uint8_t m_state = IDLE; + bool m_packet_body = false; + bool m_length_decreasing = false; + uint16_t m_packet_length = 0; + bool m_dts_present = false; + + int64_t m_scr = 0, m_scr_temp = 0; + int64_t m_pts = 0, m_pts_temp = 0; + int64_t m_dts = 0, m_dts_temp = 0; + + bool m_scr_updated = false; + bool m_pts_updated = false; + bool m_dts_updated = false; + bool m_program_end = false; +}; + +#endif // MAME_MACHINE_MPEG1DEMUX_H diff --git a/src/mame/machine/mpeg1video.cpp b/src/mame/machine/mpeg1video.cpp new file mode 100644 index 00000000..33c8914f --- /dev/null +++ b/src/mame/machine/mpeg1video.cpp @@ -0,0 +1,1936 @@ +// license:BSD-3-Clause +// copyright-holders:vibecodekun +/****************************************************************************** + + ISO/IEC 11172-2 (MPEG-1) video decoder + + See mpeg1video.h for the interface and for where the tables come from. + + Portions of this file are derived from PL_MPEG by Dominic Szablewski + (https://github.com/phoboslab/pl_mpeg), used under the following + license: + + The MIT License (MIT) + + Copyright (c) 2019 Dominic Szablewski + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. + +*******************************************************************************/ + +#include "mpeg1video.h" + +#include +#include + +namespace { + +//************************************************************************** +// Start codes +//************************************************************************** + +constexpr uint8_t START_PICTURE = 0x00; +constexpr uint8_t START_SLICE_FIRST = 0x01; +constexpr uint8_t START_SLICE_LAST = 0xaf; +constexpr uint8_t START_USER_DATA = 0xb2; +constexpr uint8_t START_SEQUENCE = 0xb3; +constexpr uint8_t START_EXTENSION = 0xb5; +constexpr uint8_t START_SEQ_END = 0xb7; +constexpr uint8_t START_GOP = 0xb8; + +constexpr int PICTURE_TYPE_INTRA = 1; +constexpr int PICTURE_TYPE_PREDICTIVE = 2; +constexpr int PICTURE_TYPE_B = 3; + +//************************************************************************** +// Tables from ISO/IEC 11172-2 +//************************************************************************** + +const uint8_t ZIG_ZAG[64] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +const uint8_t DEFAULT_INTRA_QUANT[64] = +{ + 8, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83 +}; + +const uint8_t DEFAULT_NON_INTRA_QUANT[64] = +{ + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16 +}; + +// Frame period in 90 kHz ticks for each sequence header frame rate code. +const uint16_t PICTURE_RATE_90KHZ[16] = +{ + 0, 3754, 3750, 3600, 3003, 3000, 1800, 1502, + 1500, 0, 0, 0, 0, 0, 0, 0 +}; + +} // anonymous namespace + +//************************************************************************** +// Variable length code trees +//************************************************************************** +// +// Each table is a flat binary tree. Starting from node 0, one bit at a time +// selects the left or right child. A non-zero index is the next node (already +// multiplied by two); an index of zero means the accompanying value is the +// result; -1 marks a code that cannot occur. + +using mpeg1_detail::vlc_node; +using mpeg1_detail::vlc_node_u; + +namespace { + +const vlc_node MACROBLOCK_ADDRESS_INCREMENT[] = +{ + { 1 << 1, 0}, { 0, 1}, // 0: x + { 2 << 1, 0}, { 3 << 1, 0}, // 1: 0x + { 4 << 1, 0}, { 5 << 1, 0}, // 2: 00x + { 0, 3}, { 0, 2}, // 3: 01x + { 6 << 1, 0}, { 7 << 1, 0}, // 4: 000x + { 0, 5}, { 0, 4}, // 5: 001x + { 8 << 1, 0}, { 9 << 1, 0}, // 6: 0000x + { 0, 7}, { 0, 6}, // 7: 0001x + { 10 << 1, 0}, { 11 << 1, 0}, // 8: 0000 0x + { 12 << 1, 0}, { 13 << 1, 0}, // 9: 0000 1x + { 14 << 1, 0}, { 15 << 1, 0}, // 10: 0000 00x + { 16 << 1, 0}, { 17 << 1, 0}, // 11: 0000 01x + { 18 << 1, 0}, { 19 << 1, 0}, // 12: 0000 10x + { 0, 9}, { 0, 8}, // 13: 0000 11x + { -1, 0}, { 20 << 1, 0}, // 14: 0000 000x + { -1, 0}, { 21 << 1, 0}, // 15: 0000 001x + { 22 << 1, 0}, { 23 << 1, 0}, // 16: 0000 010x + { 0, 15}, { 0, 14}, // 17: 0000 011x + { 0, 13}, { 0, 12}, // 18: 0000 100x + { 0, 11}, { 0, 10}, // 19: 0000 101x + { 24 << 1, 0}, { 25 << 1, 0}, // 20: 0000 0001x + { 26 << 1, 0}, { 27 << 1, 0}, // 21: 0000 0011x + { 28 << 1, 0}, { 29 << 1, 0}, // 22: 0000 0100x + { 30 << 1, 0}, { 31 << 1, 0}, // 23: 0000 0101x + { 32 << 1, 0}, { -1, 0}, // 24: 0000 0001 0x + { -1, 0}, { 33 << 1, 0}, // 25: 0000 0001 1x + { 34 << 1, 0}, { 35 << 1, 0}, // 26: 0000 0011 0x + { 36 << 1, 0}, { 37 << 1, 0}, // 27: 0000 0011 1x + { 38 << 1, 0}, { 39 << 1, 0}, // 28: 0000 0100 0x + { 0, 21}, { 0, 20}, // 29: 0000 0100 1x + { 0, 19}, { 0, 18}, // 30: 0000 0101 0x + { 0, 17}, { 0, 16}, // 31: 0000 0101 1x + { 0, 35}, { -1, 0}, // 32: 0000 0001 00x (35 = escape) + { -1, 0}, { 0, 34}, // 33: 0000 0001 11x (34 = stuffing) + { 0, 33}, { 0, 32}, // 34: 0000 0011 00x + { 0, 31}, { 0, 30}, // 35: 0000 0011 01x + { 0, 29}, { 0, 28}, // 36: 0000 0011 10x + { 0, 27}, { 0, 26}, // 37: 0000 0011 11x + { 0, 25}, { 0, 24}, // 38: 0000 0100 00x + { 0, 23}, { 0, 22} // 39: 0000 0100 01x +}; + +// Macroblock type flags +constexpr int MB_INTRA = 0x01; +constexpr int MB_PATTERN = 0x02; +constexpr int MB_BACKWARD = 0x04; +constexpr int MB_FORWARD = 0x08; +constexpr int MB_QUANT = 0x10; + +const vlc_node MACROBLOCK_TYPE_INTRA[] = +{ + { 1 << 1, 0}, { 0, 0x01}, // 0: x + { -1, 0}, { 0, 0x11}, // 1: 0x +}; + +const vlc_node MACROBLOCK_TYPE_PREDICTIVE[] = +{ + { 1 << 1, 0}, { 0, 0x0a}, // 0: x + { 2 << 1, 0}, { 0, 0x02}, // 1: 0x + { 3 << 1, 0}, { 0, 0x08}, // 2: 00x + { 4 << 1, 0}, { 5 << 1, 0}, // 3: 000x + { 6 << 1, 0}, { 0, 0x12}, // 4: 0000x + { 0, 0x1a}, { 0, 0x01}, // 5: 0001x + { -1, 0}, { 0, 0x11}, // 6: 0000 0x +}; + +const vlc_node MACROBLOCK_TYPE_B[] = +{ + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 3 << 1, 0}, { 4 << 1, 0}, // 1: 0x + { 0, 0x0c}, { 0, 0x0e}, // 2: 1x + { 5 << 1, 0}, { 6 << 1, 0}, // 3: 00x + { 0, 0x04}, { 0, 0x06}, // 4: 01x + { 7 << 1, 0}, { 8 << 1, 0}, // 5: 000x + { 0, 0x08}, { 0, 0x0a}, // 6: 001x + { 9 << 1, 0}, { 10 << 1, 0}, // 7: 0000x + { 0, 0x1e}, { 0, 0x01}, // 8: 0001x + { -1, 0}, { 0, 0x11}, // 9: 0000 0x + { 0, 0x16}, { 0, 0x1a}, // 10: 0000 1x +}; + +const vlc_node *const MACROBLOCK_TYPE[4] = +{ + nullptr, + MACROBLOCK_TYPE_INTRA, + MACROBLOCK_TYPE_PREDICTIVE, + MACROBLOCK_TYPE_B +}; + +const vlc_node CODE_BLOCK_PATTERN[] = +{ + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 3 << 1, 0}, { 4 << 1, 0}, // 1: 0x + { 5 << 1, 0}, { 6 << 1, 0}, // 2: 1x + { 7 << 1, 0}, { 8 << 1, 0}, // 3: 00x + { 9 << 1, 0}, { 10 << 1, 0}, // 4: 01x + { 11 << 1, 0}, { 12 << 1, 0}, // 5: 10x + { 13 << 1, 0}, { 0, 60}, // 6: 11x + { 14 << 1, 0}, { 15 << 1, 0}, // 7: 000x + { 16 << 1, 0}, { 17 << 1, 0}, // 8: 001x + { 18 << 1, 0}, { 19 << 1, 0}, // 9: 010x + { 20 << 1, 0}, { 21 << 1, 0}, // 10: 011x + { 22 << 1, 0}, { 23 << 1, 0}, // 11: 100x + { 0, 32}, { 0, 16}, // 12: 101x + { 0, 8}, { 0, 4}, // 13: 110x + { 24 << 1, 0}, { 25 << 1, 0}, // 14: 0000x + { 26 << 1, 0}, { 27 << 1, 0}, // 15: 0001x + { 28 << 1, 0}, { 29 << 1, 0}, // 16: 0010x + { 30 << 1, 0}, { 31 << 1, 0}, // 17: 0011x + { 0, 62}, { 0, 2}, // 18: 0100x + { 0, 61}, { 0, 1}, // 19: 0101x + { 0, 56}, { 0, 52}, // 20: 0110x + { 0, 44}, { 0, 28}, // 21: 0111x + { 0, 40}, { 0, 20}, // 22: 1000x + { 0, 48}, { 0, 12}, // 23: 1001x + { 32 << 1, 0}, { 33 << 1, 0}, // 24: 0000 0x + { 34 << 1, 0}, { 35 << 1, 0}, // 25: 0000 1x + { 36 << 1, 0}, { 37 << 1, 0}, // 26: 0001 0x + { 38 << 1, 0}, { 39 << 1, 0}, // 27: 0001 1x + { 40 << 1, 0}, { 41 << 1, 0}, // 28: 0010 0x + { 42 << 1, 0}, { 43 << 1, 0}, // 29: 0010 1x + { 0, 63}, { 0, 3}, // 30: 0011 0x + { 0, 36}, { 0, 24}, // 31: 0011 1x + { 44 << 1, 0}, { 45 << 1, 0}, // 32: 0000 00x + { 46 << 1, 0}, { 47 << 1, 0}, // 33: 0000 01x + { 48 << 1, 0}, { 49 << 1, 0}, // 34: 0000 10x + { 50 << 1, 0}, { 51 << 1, 0}, // 35: 0000 11x + { 52 << 1, 0}, { 53 << 1, 0}, // 36: 0001 00x + { 54 << 1, 0}, { 55 << 1, 0}, // 37: 0001 01x + { 56 << 1, 0}, { 57 << 1, 0}, // 38: 0001 10x + { 58 << 1, 0}, { 59 << 1, 0}, // 39: 0001 11x + { 0, 34}, { 0, 18}, // 40: 0010 00x + { 0, 10}, { 0, 6}, // 41: 0010 01x + { 0, 33}, { 0, 17}, // 42: 0010 10x + { 0, 9}, { 0, 5}, // 43: 0010 11x + { -1, 0}, { 60 << 1, 0}, // 44: 0000 000x + { 61 << 1, 0}, { 62 << 1, 0}, // 45: 0000 001x + { 0, 58}, { 0, 54}, // 46: 0000 010x + { 0, 46}, { 0, 30}, // 47: 0000 011x + { 0, 57}, { 0, 53}, // 48: 0000 100x + { 0, 45}, { 0, 29}, // 49: 0000 101x + { 0, 38}, { 0, 26}, // 50: 0000 110x + { 0, 37}, { 0, 25}, // 51: 0000 111x + { 0, 43}, { 0, 23}, // 52: 0001 000x + { 0, 51}, { 0, 15}, // 53: 0001 001x + { 0, 42}, { 0, 22}, // 54: 0001 010x + { 0, 50}, { 0, 14}, // 55: 0001 011x + { 0, 41}, { 0, 21}, // 56: 0001 100x + { 0, 49}, { 0, 13}, // 57: 0001 101x + { 0, 35}, { 0, 19}, // 58: 0001 110x + { 0, 11}, { 0, 7}, // 59: 0001 111x + { 0, 39}, { 0, 27}, // 60: 0000 0001x + { 0, 59}, { 0, 55}, // 61: 0000 0010x + { 0, 47}, { 0, 31}, // 62: 0000 0011x +}; + +const vlc_node MOTION[] = +{ + { 1 << 1, 0}, { 0, 0}, // 0: x + { 2 << 1, 0}, { 3 << 1, 0}, // 1: 0x + { 4 << 1, 0}, { 5 << 1, 0}, // 2: 00x + { 0, 1}, { 0, -1}, // 3: 01x + { 6 << 1, 0}, { 7 << 1, 0}, // 4: 000x + { 0, 2}, { 0, -2}, // 5: 001x + { 8 << 1, 0}, { 9 << 1, 0}, // 6: 0000x + { 0, 3}, { 0, -3}, // 7: 0001x + { 10 << 1, 0}, { 11 << 1, 0}, // 8: 0000 0x + { 12 << 1, 0}, { 13 << 1, 0}, // 9: 0000 1x + { -1, 0}, { 14 << 1, 0}, // 10: 0000 00x + { 15 << 1, 0}, { 16 << 1, 0}, // 11: 0000 01x + { 17 << 1, 0}, { 18 << 1, 0}, // 12: 0000 10x + { 0, 4}, { 0, -4}, // 13: 0000 11x + { -1, 0}, { 19 << 1, 0}, // 14: 0000 001x + { 20 << 1, 0}, { 21 << 1, 0}, // 15: 0000 010x + { 0, 7}, { 0, -7}, // 16: 0000 011x + { 0, 6}, { 0, -6}, // 17: 0000 100x + { 0, 5}, { 0, -5}, // 18: 0000 101x + { 22 << 1, 0}, { 23 << 1, 0}, // 19: 0000 0011x + { 24 << 1, 0}, { 25 << 1, 0}, // 20: 0000 0100x + { 26 << 1, 0}, { 27 << 1, 0}, // 21: 0000 0101x + { 28 << 1, 0}, { 29 << 1, 0}, // 22: 0000 0011 0x + { 30 << 1, 0}, { 31 << 1, 0}, // 23: 0000 0011 1x + { 32 << 1, 0}, { 33 << 1, 0}, // 24: 0000 0100 0x + { 0, 10}, { 0, -10}, // 25: 0000 0100 1x + { 0, 9}, { 0, -9}, // 26: 0000 0101 0x + { 0, 8}, { 0, -8}, // 27: 0000 0101 1x + { 0, 16}, { 0, -16}, // 28: 0000 0011 00x + { 0, 15}, { 0, -15}, // 29: 0000 0011 01x + { 0, 14}, { 0, -14}, // 30: 0000 0011 10x + { 0, 13}, { 0, -13}, // 31: 0000 0011 11x + { 0, 12}, { 0, -12}, // 32: 0000 0100 00x + { 0, 11}, { 0, -11}, // 33: 0000 0100 01x +}; + +const vlc_node DCT_SIZE_LUMINANCE[] = +{ + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 0, 1}, { 0, 2}, // 1: 0x + { 3 << 1, 0}, { 4 << 1, 0}, // 2: 1x + { 0, 0}, { 0, 3}, // 3: 10x + { 0, 4}, { 5 << 1, 0}, // 4: 11x + { 0, 5}, { 6 << 1, 0}, // 5: 111x + { 0, 6}, { 7 << 1, 0}, // 6: 1111x + { 0, 7}, { 8 << 1, 0}, // 7: 1111 1x + { 0, 8}, { -1, 0}, // 8: 1111 11x +}; + +const vlc_node DCT_SIZE_CHROMINANCE[] = +{ + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 0, 0}, { 0, 1}, // 1: 0x + { 0, 2}, { 3 << 1, 0}, // 2: 1x + { 0, 3}, { 4 << 1, 0}, // 3: 11x + { 0, 4}, { 5 << 1, 0}, // 4: 111x + { 0, 5}, { 6 << 1, 0}, // 5: 1111x + { 0, 6}, { 7 << 1, 0}, // 6: 1111 1x + { 0, 7}, { 8 << 1, 0}, // 7: 1111 11x + { 0, 8}, { -1, 0}, // 8: 1111 111x +}; + +const vlc_node *const DCT_SIZE[3] = +{ + DCT_SIZE_LUMINANCE, + DCT_SIZE_CHROMINANCE, + DCT_SIZE_CHROMINANCE +}; + +// Values are packed as 0xRRLL: run in the high byte, level in the low byte. +// 0xffff is the escape code; 0x0000 marks an impossible code. +constexpr uint16_t DCT_COEFF_ESCAPE = 0xffff; + +const vlc_node_u DCT_COEFF[] = +{ + { 1 << 1, 0}, { 0, 0x0001}, // 0: x + { 2 << 1, 0}, { 3 << 1, 0}, // 1: 0x + { 4 << 1, 0}, { 5 << 1, 0}, // 2: 00x + { 6 << 1, 0}, { 0, 0x0101}, // 3: 01x + { 7 << 1, 0}, { 8 << 1, 0}, // 4: 000x + { 9 << 1, 0}, { 10 << 1, 0}, // 5: 001x + { 0, 0x0002}, { 0, 0x0201}, // 6: 010x + { 11 << 1, 0}, { 12 << 1, 0}, // 7: 0000x + { 13 << 1, 0}, { 14 << 1, 0}, // 8: 0001x + { 15 << 1, 0}, { 0, 0x0003}, // 9: 0010x + { 0, 0x0401}, { 0, 0x0301}, // 10: 0011x + { 16 << 1, 0}, { 0, 0xffff}, // 11: 0000 0x + { 17 << 1, 0}, { 18 << 1, 0}, // 12: 0000 1x + { 0, 0x0701}, { 0, 0x0601}, // 13: 0001 0x + { 0, 0x0102}, { 0, 0x0501}, // 14: 0001 1x + { 19 << 1, 0}, { 20 << 1, 0}, // 15: 0010 0x + { 21 << 1, 0}, { 22 << 1, 0}, // 16: 0000 00x + { 0, 0x0202}, { 0, 0x0901}, // 17: 0000 10x + { 0, 0x0004}, { 0, 0x0801}, // 18: 0000 11x + { 23 << 1, 0}, { 24 << 1, 0}, // 19: 0010 00x + { 25 << 1, 0}, { 26 << 1, 0}, // 20: 0010 01x + { 27 << 1, 0}, { 28 << 1, 0}, // 21: 0000 000x + { 29 << 1, 0}, { 30 << 1, 0}, // 22: 0000 001x + { 0, 0x0d01}, { 0, 0x0006}, // 23: 0010 000x + { 0, 0x0c01}, { 0, 0x0b01}, // 24: 0010 001x + { 0, 0x0302}, { 0, 0x0103}, // 25: 0010 010x + { 0, 0x0005}, { 0, 0x0a01}, // 26: 0010 011x + { 31 << 1, 0}, { 32 << 1, 0}, // 27: 0000 0000x + { 33 << 1, 0}, { 34 << 1, 0}, // 28: 0000 0001x + { 35 << 1, 0}, { 36 << 1, 0}, // 29: 0000 0010x + { 37 << 1, 0}, { 38 << 1, 0}, // 30: 0000 0011x + { 39 << 1, 0}, { 40 << 1, 0}, // 31: 0000 0000 0x + { 41 << 1, 0}, { 42 << 1, 0}, // 32: 0000 0000 1x + { 43 << 1, 0}, { 44 << 1, 0}, // 33: 0000 0001 0x + { 45 << 1, 0}, { 46 << 1, 0}, // 34: 0000 0001 1x + { 0, 0x1001}, { 0, 0x0502}, // 35: 0000 0010 0x + { 0, 0x0007}, { 0, 0x0203}, // 36: 0000 0010 1x + { 0, 0x0104}, { 0, 0x0f01}, // 37: 0000 0011 0x + { 0, 0x0e01}, { 0, 0x0402}, // 38: 0000 0011 1x + { 47 << 1, 0}, { 48 << 1, 0}, // 39: 0000 0000 00x + { 49 << 1, 0}, { 50 << 1, 0}, // 40: 0000 0000 01x + { 51 << 1, 0}, { 52 << 1, 0}, // 41: 0000 0000 10x + { 53 << 1, 0}, { 54 << 1, 0}, // 42: 0000 0000 11x + { 55 << 1, 0}, { 56 << 1, 0}, // 43: 0000 0001 00x + { 57 << 1, 0}, { 58 << 1, 0}, // 44: 0000 0001 01x + { 59 << 1, 0}, { 60 << 1, 0}, // 45: 0000 0001 10x + { 61 << 1, 0}, { 62 << 1, 0}, // 46: 0000 0001 11x + { -1, 0}, { 63 << 1, 0}, // 47: 0000 0000 000x + { 64 << 1, 0}, { 65 << 1, 0}, // 48: 0000 0000 001x + { 66 << 1, 0}, { 67 << 1, 0}, // 49: 0000 0000 010x + { 68 << 1, 0}, { 69 << 1, 0}, // 50: 0000 0000 011x + { 70 << 1, 0}, { 71 << 1, 0}, // 51: 0000 0000 100x + { 72 << 1, 0}, { 73 << 1, 0}, // 52: 0000 0000 101x + { 74 << 1, 0}, { 75 << 1, 0}, // 53: 0000 0000 110x + { 76 << 1, 0}, { 77 << 1, 0}, // 54: 0000 0000 111x + { 0, 0x000b}, { 0, 0x0802}, // 55: 0000 0001 000x + { 0, 0x0403}, { 0, 0x000a}, // 56: 0000 0001 001x + { 0, 0x0204}, { 0, 0x0702}, // 57: 0000 0001 010x + { 0, 0x1501}, { 0, 0x1401}, // 58: 0000 0001 011x + { 0, 0x0009}, { 0, 0x1301}, // 59: 0000 0001 100x + { 0, 0x1201}, { 0, 0x0105}, // 60: 0000 0001 101x + { 0, 0x0303}, { 0, 0x0008}, // 61: 0000 0001 110x + { 0, 0x0602}, { 0, 0x1101}, // 62: 0000 0001 111x + { 78 << 1, 0}, { 79 << 1, 0}, // 63: 0000 0000 0001x + { 80 << 1, 0}, { 81 << 1, 0}, // 64: 0000 0000 0010x + { 82 << 1, 0}, { 83 << 1, 0}, // 65: 0000 0000 0011x + { 84 << 1, 0}, { 85 << 1, 0}, // 66: 0000 0000 0100x + { 86 << 1, 0}, { 87 << 1, 0}, // 67: 0000 0000 0101x + { 88 << 1, 0}, { 89 << 1, 0}, // 68: 0000 0000 0110x + { 90 << 1, 0}, { 91 << 1, 0}, // 69: 0000 0000 0111x + { 0, 0x0a02}, { 0, 0x0902}, // 70: 0000 0000 1000x + { 0, 0x0503}, { 0, 0x0304}, // 71: 0000 0000 1001x + { 0, 0x0205}, { 0, 0x0107}, // 72: 0000 0000 1010x + { 0, 0x0106}, { 0, 0x000f}, // 73: 0000 0000 1011x + { 0, 0x000e}, { 0, 0x000d}, // 74: 0000 0000 1100x + { 0, 0x000c}, { 0, 0x1a01}, // 75: 0000 0000 1101x + { 0, 0x1901}, { 0, 0x1801}, // 76: 0000 0000 1110x + { 0, 0x1701}, { 0, 0x1601}, // 77: 0000 0000 1111x + { 92 << 1, 0}, { 93 << 1, 0}, // 78: 0000 0000 0001 0x + { 94 << 1, 0}, { 95 << 1, 0}, // 79: 0000 0000 0001 1x + { 96 << 1, 0}, { 97 << 1, 0}, // 80: 0000 0000 0010 0x + { 98 << 1, 0}, { 99 << 1, 0}, // 81: 0000 0000 0010 1x + { 100 << 1, 0}, { 101 << 1, 0}, // 82: 0000 0000 0011 0x + { 102 << 1, 0}, { 103 << 1, 0}, // 83: 0000 0000 0011 1x + { 0, 0x001f}, { 0, 0x001e}, // 84: 0000 0000 0100 0x + { 0, 0x001d}, { 0, 0x001c}, // 85: 0000 0000 0100 1x + { 0, 0x001b}, { 0, 0x001a}, // 86: 0000 0000 0101 0x + { 0, 0x0019}, { 0, 0x0018}, // 87: 0000 0000 0101 1x + { 0, 0x0017}, { 0, 0x0016}, // 88: 0000 0000 0110 0x + { 0, 0x0015}, { 0, 0x0014}, // 89: 0000 0000 0110 1x + { 0, 0x0013}, { 0, 0x0012}, // 90: 0000 0000 0111 0x + { 0, 0x0011}, { 0, 0x0010}, // 91: 0000 0000 0111 1x + { 104 << 1, 0}, { 105 << 1, 0}, // 92: 0000 0000 0001 00x + { 106 << 1, 0}, { 107 << 1, 0}, // 93: 0000 0000 0001 01x + { 108 << 1, 0}, { 109 << 1, 0}, // 94: 0000 0000 0001 10x + { 110 << 1, 0}, { 111 << 1, 0}, // 95: 0000 0000 0001 11x + { 0, 0x0028}, { 0, 0x0027}, // 96: 0000 0000 0010 00x + { 0, 0x0026}, { 0, 0x0025}, // 97: 0000 0000 0010 01x + { 0, 0x0024}, { 0, 0x0023}, // 98: 0000 0000 0010 10x + { 0, 0x0022}, { 0, 0x0021}, // 99: 0000 0000 0010 11x + { 0, 0x0020}, { 0, 0x010e}, // 100: 0000 0000 0011 00x + { 0, 0x010d}, { 0, 0x010c}, // 101: 0000 0000 0011 01x + { 0, 0x010b}, { 0, 0x010a}, // 102: 0000 0000 0011 10x + { 0, 0x0109}, { 0, 0x0108}, // 103: 0000 0000 0011 11x + { 0, 0x0112}, { 0, 0x0111}, // 104: 0000 0000 0001 000x + { 0, 0x0110}, { 0, 0x010f}, // 105: 0000 0000 0001 001x + { 0, 0x0603}, { 0, 0x1002}, // 106: 0000 0000 0001 010x + { 0, 0x0f02}, { 0, 0x0e02}, // 107: 0000 0000 0001 011x + { 0, 0x0d02}, { 0, 0x0c02}, // 108: 0000 0000 0001 100x + { 0, 0x0b02}, { 0, 0x1f01}, // 109: 0000 0000 0001 101x + { 0, 0x1e01}, { 0, 0x1d01}, // 110: 0000 0000 0001 110x + { 0, 0x1c01}, { 0, 0x1b01}, // 111: 0000 0000 0001 111x +}; + +//************************************************************************** +// Inverse DCT +//************************************************************************** +// +// Separable integer IDCT after Chen, Fralick and Smith as refined by Wang, +// which is the formulation used by the ISO reference decoder. + +constexpr int W1 = 2841; // 2048 * sqrt(2) * cos(1 * pi / 16) +constexpr int W2 = 2676; // 2048 * sqrt(2) * cos(2 * pi / 16) +constexpr int W3 = 2408; // 2048 * sqrt(2) * cos(3 * pi / 16) +constexpr int W5 = 1609; // 2048 * sqrt(2) * cos(5 * pi / 16) +constexpr int W6 = 1108; // 2048 * sqrt(2) * cos(6 * pi / 16) +constexpr int W7 = 565; // 2048 * sqrt(2) * cos(7 * pi / 16) + +void idct_row(int *blk) +{ + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + + x1 = blk[4] << 11; + x2 = blk[6]; + x3 = blk[2]; + x4 = blk[1]; + x5 = blk[7]; + x6 = blk[5]; + x7 = blk[3]; + + if (!(x1 | x2 | x3 | x4 | x5 | x6 | x7)) + { + const int dc = blk[0] << 3; + for (int i = 0; i < 8; i++) + blk[i] = dc; + return; + } + + x0 = (blk[0] << 11) + 128; + + x8 = W7 * (x4 + x5); + x4 = x8 + (W1 - W7) * x4; + x5 = x8 - (W1 + W7) * x5; + x8 = W3 * (x6 + x7); + x6 = x8 - (W3 - W5) * x6; + x7 = x8 - (W3 + W5) * x7; + + x8 = x0 + x1; + x0 -= x1; + x1 = W6 * (x3 + x2); + x2 = x1 - (W2 + W6) * x2; + x3 = x1 + (W2 - W6) * x3; + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = (181 * (x4 + x5) + 128) >> 8; + x4 = (181 * (x4 - x5) + 128) >> 8; + + blk[0] = (x7 + x1) >> 8; + blk[1] = (x3 + x2) >> 8; + blk[2] = (x0 + x4) >> 8; + blk[3] = (x8 + x6) >> 8; + blk[4] = (x8 - x6) >> 8; + blk[5] = (x0 - x4) >> 8; + blk[6] = (x3 - x2) >> 8; + blk[7] = (x7 - x1) >> 8; +} + +void idct_col(int *blk) +{ + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + + x1 = blk[8 * 4] << 8; + x2 = blk[8 * 6]; + x3 = blk[8 * 2]; + x4 = blk[8 * 1]; + x5 = blk[8 * 7]; + x6 = blk[8 * 5]; + x7 = blk[8 * 3]; + + if (!(x1 | x2 | x3 | x4 | x5 | x6 | x7)) + { + const int dc = (blk[8 * 0] + 32) >> 6; + for (int i = 0; i < 8; i++) + blk[8 * i] = dc; + return; + } + + x0 = (blk[8 * 0] << 8) + 8192; + + x8 = W7 * (x4 + x5) + 4; + x4 = (x8 + (W1 - W7) * x4) >> 3; + x5 = (x8 - (W1 + W7) * x5) >> 3; + x8 = W3 * (x6 + x7) + 4; + x6 = (x8 - (W3 - W5) * x6) >> 3; + x7 = (x8 - (W3 + W5) * x7) >> 3; + + x8 = x0 + x1; + x0 -= x1; + x1 = W6 * (x3 + x2) + 4; + x2 = (x1 - (W2 + W6) * x2) >> 3; + x3 = (x1 + (W2 - W6) * x3) >> 3; + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = (181 * (x4 + x5) + 128) >> 8; + x4 = (181 * (x4 - x5) + 128) >> 8; + + blk[8 * 0] = (x7 + x1) >> 14; + blk[8 * 1] = (x3 + x2) >> 14; + blk[8 * 2] = (x0 + x4) >> 14; + blk[8 * 3] = (x8 + x6) >> 14; + blk[8 * 4] = (x8 - x6) >> 14; + blk[8 * 5] = (x0 - x4) >> 14; + blk[8 * 6] = (x3 - x2) >> 14; + blk[8 * 7] = (x7 - x1) >> 14; +} + +inline uint8_t clamp_pixel(int value) +{ + return uint8_t((value < 0) ? 0 : ((value > 255) ? 255 : value)); +} + +} // anonymous namespace + +//************************************************************************** +// Construction +//************************************************************************** + +mpeg1_video_decoder::mpeg1_video_decoder() +{ + std::memcpy(m_intra_quant, DEFAULT_INTRA_QUANT, sizeof(m_intra_quant)); + std::memcpy(m_non_intra_quant, DEFAULT_NON_INTRA_QUANT, sizeof(m_non_intra_quant)); + std::memset(m_block_data, 0, sizeof(m_block_data)); + reset(); +} + +void mpeg1_video_decoder::reset() +{ + m_buffer.clear(); + m_read_pos = 0; + m_bit_pos = 0; + + m_has_sequence = false; + m_sequence_ended = false; + m_width = m_height = 0; + m_mb_width = m_mb_height = 0; + m_luma_width = m_luma_height = 0; + m_chroma_width = m_chroma_height = 0; + m_frame_rate_index = 0; + + std::memcpy(m_intra_quant, DEFAULT_INTRA_QUANT, sizeof(m_intra_quant)); + std::memcpy(m_non_intra_quant, DEFAULT_NON_INTRA_QUANT, sizeof(m_non_intra_quant)); + + m_picture_type = 0; + m_temporal_reference = 0; + m_gop_timecode = 0; + m_slice_begin = false; + m_display_frame = nullptr; + + for (frame &f : m_frames) + f = frame(); + + m_work = &m_frames[0]; + m_past = &m_frames[1]; + m_future = &m_frames[2]; +} + +void mpeg1_video_decoder::clear_fifo() +{ + sequence_state sequence; + get_sequence_state(sequence); + reset(); + set_sequence_state(sequence); +} + +uint16_t mpeg1_video_decoder::frame_period_90khz() const +{ + return PICTURE_RATE_90KHZ[m_frame_rate_index & 0x0f]; +} + +void mpeg1_video_decoder::get_sequence_state(sequence_state &state) const +{ + state.valid = m_has_sequence ? 1 : 0; + state.width = uint16_t(m_width); + state.height = uint16_t(m_height); + state.frame_rate_index = m_frame_rate_index; + std::memcpy(state.intra_quant, m_intra_quant, sizeof(state.intra_quant)); + std::memcpy(state.non_intra_quant, m_non_intra_quant, sizeof(state.non_intra_quant)); +} + +void mpeg1_video_decoder::set_sequence_state(const sequence_state &state) +{ + if (!state.valid || !state.width || !state.height) + return; + + m_frame_rate_index = state.frame_rate_index; + std::memcpy(m_intra_quant, state.intra_quant, sizeof(m_intra_quant)); + std::memcpy(m_non_intra_quant, state.non_intra_quant, sizeof(m_non_intra_quant)); + + if (int(state.width) != m_width || int(state.height) != m_height) + { + m_width = state.width; + m_height = state.height; + m_mb_width = (m_width + 15) / 16; + m_mb_height = (m_height + 15) / 16; + m_luma_width = m_mb_width * 16; + m_luma_height = m_mb_height * 16; + m_chroma_width = m_luma_width / 2; + m_chroma_height = m_luma_height / 2; + allocate_frames(); + } + + // The reference pictures are gone, so the first few frames after this will + // be wrong until the next intra picture arrives. That is the same thing + // hardware does when you seek. + m_has_sequence = true; +} + +void mpeg1_video_decoder::get_decoder_state(decoder_state &state) const +{ + // Rebase the input buffer so the read position becomes byte 0. Nothing + // before it is ever looked at again, and dropping it is what keeps the + // saved copy inside the buffer limit even when compaction is overdue. + const size_t size = m_buffer.size() - m_read_pos; + state.buffer_size = uint32_t(size); + state.bit_pos = uint32_t(m_bit_pos - m_read_pos * 8); + if (size != 0) + std::memcpy(state.buffer, m_buffer.data() + m_read_pos, size); + // Unused bytes are serialized too. Do not retain data from an earlier + // save: speculative/replayed frames can otherwise change the state file + // even when the live decoder, audio and picture are identical. + std::memset(state.buffer + size, 0, decoder_state::max_buffer - size); + + state.sequence_ended = m_sequence_ended ? 1 : 0; + state.picture_type = m_picture_type | (m_gop_timecode << 3); + state.temporal_reference = m_temporal_reference; + + const auto index_of = [this](const frame *f) -> int32_t + { + return (f == nullptr) ? -1 : int32_t(f - m_frames); + }; + + state.past_index = index_of(m_past); + state.future_index = index_of(m_future); + state.work_index = index_of(m_work); + state.display_index = index_of(m_display_frame); + + const size_t luma = size_t(m_luma_width) * m_luma_height; + const size_t chroma = size_t(m_chroma_width) * m_chroma_height; + state.frames_saved = (luma != 0 && luma <= decoder_state::max_luma && chroma <= decoder_state::max_chroma) ? 1 : 0; + + for (int i = 0; i < 3; i++) + { + state.frame_valid[i] = m_frames[i].valid ? 1 : 0; + state.frame_picture_type[i] = m_frames[i].picture_type | (m_frames[i].timecode << 3); + state.frame_temporal_reference[i] = m_frames[i].temporal_reference; + + if (state.frames_saved == 0 || m_frames[i].y.size() != luma) + continue; + + std::memcpy(state.frame_y[i], m_frames[i].y.data(), luma); + std::memcpy(state.frame_cb[i], m_frames[i].cb.data(), chroma); + std::memcpy(state.frame_cr[i], m_frames[i].cr.data(), chroma); + } +} + +void mpeg1_video_decoder::set_decoder_state(const decoder_state &state) +{ + const size_t size = std::min(state.buffer_size, decoder_state::max_buffer); + m_buffer.assign(state.buffer, state.buffer + size); + m_read_pos = 0; + m_bit_pos = std::min(state.bit_pos, size * 8); + + m_sequence_ended = state.sequence_ended != 0; + m_picture_type = state.picture_type & 7; + m_gop_timecode = uint32_t(state.picture_type) >> 3; + m_temporal_reference = state.temporal_reference; + + const auto frame_at = [this](int32_t index) -> frame * + { + return (index >= 0 && index < 3) ? &m_frames[index] : nullptr; + }; + + // The three working pictures are never null in a state written by + // get_decoder_state(); fall back to the reset assignment rather than + // dereference a null if one ever arrives from elsewhere. + m_work = frame_at(state.work_index); + m_past = frame_at(state.past_index); + m_future = frame_at(state.future_index); + m_display_frame = frame_at(state.display_index); + + if (m_work == nullptr) + m_work = &m_frames[0]; + if (m_past == nullptr) + m_past = &m_frames[1]; + if (m_future == nullptr) + m_future = &m_frames[2]; + + for (int i = 0; i < 3; i++) + { + m_frames[i].picture_type = state.frame_picture_type[i] & 7; + m_frames[i].timecode = uint32_t(state.frame_picture_type[i]) >> 3; + m_frames[i].temporal_reference = state.frame_temporal_reference[i]; + m_frames[i].valid = state.frame_valid[i] != 0; + } + + // Without usable reference pictures, say so rather than predict from + // whatever the freshly allocated buffers hold. The stream then picks up + // again at the next intra picture. + const size_t luma = size_t(m_luma_width) * m_luma_height; + const size_t chroma = size_t(m_chroma_width) * m_chroma_height; + const bool restorable = state.frames_saved != 0 && luma != 0 + && luma <= decoder_state::max_luma && chroma <= decoder_state::max_chroma; + + for (int i = 0; i < 3; i++) + { + if (restorable && m_frames[i].y.size() == luma) + { + std::memcpy(m_frames[i].y.data(), state.frame_y[i], luma); + std::memcpy(m_frames[i].cb.data(), state.frame_cb[i], chroma); + std::memcpy(m_frames[i].cr.data(), state.frame_cr[i], chroma); + } + else + { + m_frames[i].valid = false; + } + } + + if (m_display_frame != nullptr && !m_display_frame->valid) + m_display_frame = nullptr; +} + +//************************************************************************** +// Input buffer +//************************************************************************** + +void mpeg1_video_decoder::write(const uint8_t *data, size_t length) +{ + // Reclaim whatever has already been decoded before deciding there is no + // room. Only bytes ahead of the read position are still needed, so compact + // as soon as the raw vector would run past the limit; that keeps the test + // below a test of how much undecoded data there is, which is the same thing + // buffer_full() reports. + if (m_read_pos && m_buffer.size() + length > m_buffer_limit) + { + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_read_pos); + m_bit_pos -= m_read_pos * 8; + m_read_pos = 0; + } + + if (m_buffer.size() + length > m_buffer_limit) + { + // The cartridge's MPEG buffer is finite, and a full one stops accepting + // data. Reaching here means the driver kept sending after SYS_STS told + // it to stop, since buffer_full() goes true with room still to spare. + // Dropping the oldest undecoded bytes instead would tear a hole out of + // the middle of a picture and desynchronise the bit reader, which shows + // up as video skipping ahead for no visible reason. + return; + } + + m_buffer.insert(m_buffer.end(), data, data + length); +} + +bool mpeg1_video_decoder::have_bits(size_t count) const +{ + return m_bit_pos + count <= m_buffer.size() * 8; +} + +uint32_t mpeg1_video_decoder::read_bits(int count) +{ + uint32_t value = 0; + while (count--) + value = (value << 1) | uint32_t(read_bit()); + return value; +} + +int mpeg1_video_decoder::read_bit() +{ + if (m_bit_pos >= m_buffer.size() * 8) + { + m_bit_pos++; + return 0; + } + + const int bit = (m_buffer[m_bit_pos >> 3] >> (7 - (m_bit_pos & 7))) & 1; + m_bit_pos++; + return bit; +} + +void mpeg1_video_decoder::align_to_byte() +{ + m_bit_pos = (m_bit_pos + 7) & ~size_t(7); +} + +int mpeg1_video_decoder::read_vlc(const vlc_node *table) +{ + vlc_node state = { 0, 0 }; + do + { + state = table[state.index + read_bit()]; + } while (state.index > 0); + + return state.value; +} + +uint16_t mpeg1_video_decoder::read_vlc_uint(const vlc_node_u *table) +{ + vlc_node_u state = { 0, 0 }; + do + { + state = table[state.index + read_bit()]; + } while (state.index > 0); + + return state.value; +} + +size_t mpeg1_video_decoder::find_start_code(size_t from) const +{ + if (m_buffer.size() < 4) + return SIZE_MAX; + + for (size_t i = from; i + 3 < m_buffer.size(); i++) + { + if (m_buffer[i] == 0x00 && m_buffer[i + 1] == 0x00 && m_buffer[i + 2] == 0x01) + return i; + } + + return SIZE_MAX; +} + +bool mpeg1_video_decoder::at_start_code() const +{ + // Slices are padded with zero bits to a byte boundary, so a start code is + // indicated by the next 23 bits being zero. + size_t pos = m_bit_pos; + for (int i = 0; i < 23; i++, pos++) + { + if (pos >= m_buffer.size() * 8) + return true; + if ((m_buffer[pos >> 3] >> (7 - (pos & 7))) & 1) + return false; + } + return true; +} + +void mpeg1_video_decoder::discard_consumed() +{ + m_read_pos = m_bit_pos >> 3; + + // Keep the buffer from growing without bound by dropping fully consumed + // bytes once enough have accumulated. + if (m_read_pos >= 65536) + { + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_read_pos); + m_bit_pos -= m_read_pos * 8; + m_read_pos = 0; + } +} + +int mpeg1_video_decoder::pictures_buffered() const +{ + int count = 0; + for (size_t i = m_read_pos; i + 3 < m_buffer.size(); i++) + { + if (m_buffer[i] == 0x00 && m_buffer[i + 1] == 0x00 && m_buffer[i + 2] == 0x01 && m_buffer[i + 3] == START_PICTURE) + count++; + } + return count; +} + +//************************************************************************** +// Headers +//************************************************************************** + +bool mpeg1_video_decoder::decode_sequence_header() +{ + // horizontal 12, vertical 12, aspect 4, frame rate 4, bit rate 18, + // marker 1, vbv 10, constrained 1, then the two optional matrices. + if (!have_bits(64)) + return false; + + const int width = int(read_bits(12)); + const int height = int(read_bits(12)); + read_bits(4); // aspect ratio + const uint8_t frame_rate_index = uint8_t(read_bits(4)); + read_bits(18); // bit rate + read_bits(1); // marker + read_bits(10); // vbv buffer size + read_bits(1); // constrained parameters + + // DMA can stop anywhere in either optional matrix. Do not publish a + // partial header or read beyond the input: decode() rewinds to the start + // code and retries when more bytes arrive. Otherwise discard_consumed() + // can put m_read_pos past the buffer and the next save underflows its size. + uint8_t intra_quant[64]; + uint8_t non_intra_quant[64]; + std::memcpy(intra_quant, m_has_sequence ? m_intra_quant : DEFAULT_INTRA_QUANT, sizeof(intra_quant)); + std::memcpy(non_intra_quant, m_has_sequence ? m_non_intra_quant : DEFAULT_NON_INTRA_QUANT, sizeof(non_intra_quant)); + + if (read_bit()) + { + if (!have_bits(64 * 8 + 1)) // matrix plus the non-intra matrix flag + return false; + for (int i = 0; i < 64; i++) + intra_quant[ZIG_ZAG[i]] = uint8_t(read_bits(8)); + } + + if (read_bit()) + { + if (!have_bits(64 * 8)) + return false; + for (int i = 0; i < 64; i++) + non_intra_quant[ZIG_ZAG[i]] = uint8_t(read_bits(8)); + } + + m_frame_rate_index = frame_rate_index; + std::memcpy(m_intra_quant, intra_quant, sizeof(m_intra_quant)); + std::memcpy(m_non_intra_quant, non_intra_quant, sizeof(m_non_intra_quant)); + + if (width != m_width || height != m_height) + { + m_width = width; + m_height = height; + m_mb_width = (width + 15) / 16; + m_mb_height = (height + 15) / 16; + m_luma_width = m_mb_width * 16; + m_luma_height = m_mb_height * 16; + m_chroma_width = m_luma_width / 2; + m_chroma_height = m_luma_height / 2; + allocate_frames(); + } + + m_has_sequence = true; + return true; +} + +void mpeg1_video_decoder::allocate_frames() +{ + for (frame &f : m_frames) + { + f.y.assign(size_t(m_luma_width) * m_luma_height, 0); + f.cb.assign(size_t(m_chroma_width) * m_chroma_height, 128); + f.cr.assign(size_t(m_chroma_width) * m_chroma_height, 128); + f.valid = false; + } + m_display_frame = nullptr; +} + +void mpeg1_video_decoder::decode_gop_header() +{ + read_bits(1); // drop-frame flag + const uint32_t hours_minutes = read_bits(11); + read_bits(1); // marker + const uint32_t seconds_pictures = read_bits(12); + m_gop_timecode = (seconds_pictures << 16) | hours_minutes; + read_bits(1); // closed gop + read_bits(1); // broken link +} + +//************************************************************************** +// Top level +//************************************************************************** + +bool mpeg1_video_decoder::decode() +{ + for (;;) + { + const size_t code_pos = find_start_code(m_bit_pos >> 3); + if (code_pos == SIZE_MAX) + { + discard_consumed(); + return false; + } + + const uint8_t code = m_buffer[code_pos + 3]; + m_bit_pos = (code_pos + 4) * 8; + + if (code == START_SEQUENCE) + { + if (!decode_sequence_header()) + { + m_bit_pos = code_pos * 8; + return false; + } + } + else if (code == START_GOP) + { + if (!have_bits(27)) + { + m_bit_pos = code_pos * 8; + return false; + } + decode_gop_header(); + } + else if (code == START_SEQ_END) + { + m_sequence_ended = true; + } + else if (code == START_USER_DATA || code == START_EXTENSION) + { + // Skipped; the following start code search steps over the payload. + } + else if (code == START_PICTURE) + { + if (!m_has_sequence) + continue; + + // A picture can only be decoded once all of it is buffered, which + // is the case when another start code that ends it has arrived. + size_t end = code_pos + 4; + for (;;) + { + end = find_start_code(end); + if (end == SIZE_MAX) + break; + + const uint8_t next = m_buffer[end + 3]; + if (next == START_PICTURE || next == START_GOP || next == START_SEQUENCE || next == START_SEQ_END) + break; + + end += 4; + } + + if (end == SIZE_MAX) + { + // Rewind to the picture start code and wait for more data. + m_bit_pos = code_pos * 8; + return false; + } + + if (!decode_picture()) + { + m_bit_pos = code_pos * 8; + return false; + } + + m_bit_pos = end * 8; + discard_consumed(); + return true; + } + } +} + +void mpeg1_video_decoder::display_decoded_frame() +{ + // decode_picture() rotates I/P pictures into m_future; B pictures stay + // in m_work. Keep those reference buffers intact for later decoding. + frame *const decoded = (m_picture_type == PICTURE_TYPE_B) ? m_work : m_future; + m_display_frame = decoded && decoded->valid ? decoded : nullptr; +} + +bool mpeg1_video_decoder::decode_picture() +{ + if (!have_bits(29)) + return false; + + m_temporal_reference = int(read_bits(10)); + m_picture_type = int(read_bits(3)); + read_bits(16); // vbv delay + + if (m_picture_type != PICTURE_TYPE_INTRA && + m_picture_type != PICTURE_TYPE_PREDICTIVE && + m_picture_type != PICTURE_TYPE_B) + { + return false; + } + + if (m_picture_type == PICTURE_TYPE_PREDICTIVE || m_picture_type == PICTURE_TYPE_B) + { + m_full_pel_forward = read_bit() != 0; + m_forward_f_code = int(read_bits(3)); + if (m_forward_f_code == 0) + return false; + m_forward_r_size = m_forward_f_code - 1; + m_forward_f = 1 << m_forward_r_size; + } + + if (m_picture_type == PICTURE_TYPE_B) + { + m_full_pel_backward = read_bit() != 0; + m_backward_f_code = int(read_bits(3)); + if (m_backward_f_code == 0) + return false; + m_backward_r_size = m_backward_f_code - 1; + m_backward_f = 1 << m_backward_r_size; + } + + while (read_bit()) // extra_bit_picture + read_bits(8); + + // Nothing has been written to this picture yet. The address has to start + // from a known point so that the gap check below can tell which macroblocks + // the slices have actually covered. + m_macroblock_address = -1; + // A seek can enter an open GOP: the I picture is self-contained, but + // leading B pictures may still reference the anchor before the seek. + // Prediction invalidates this picture if any required reference is absent. + // Invalid P anchors must also stay invalid until an I picture recovers. + m_work->valid = true; + + int last_vertical_position = 0; + bool slice_failed = false; + + // Decode every slice belonging to this picture. + for (;;) + { + const size_t code_pos = find_start_code(m_bit_pos >> 3); + if (code_pos == SIZE_MAX) + break; + + const uint8_t code = m_buffer[code_pos + 3]; + if (code < START_SLICE_FIRST || code > START_SLICE_LAST) + break; + + // Slices run down the picture in order, so a vertical position that + // goes backwards belongs to no slice at all: it is a byte-aligned + // 00 00 01 pattern sitting in damaged slice data, which the rule against + // start code emulation only excludes for macroblocks that were validly + // coded to begin with. Decoding one puts the address back at the top of + // the picture and overwrites the rows that came out fine - one damaged + // slice cost 172 of this stream's 216 macroblocks that way. Once a slice + // has failed the test is stricter, because any pattern found next is + // being read out of the wreckage of the slice that just failed. + if (int(code) < last_vertical_position || + (slice_failed && int(code) <= last_vertical_position)) + break; + last_vertical_position = int(code); + + // Whatever the slices so far have left uncovered - because one of them + // was abandoned part way through - is filled in before this slice moves + // the address to its own row. A slice that merely starts late in its + // row leaves no gap, because the slice before it ran on past the end of + // its own row and already covered those macroblocks. + conceal_through((int(code) - 1) * m_mb_width - 1); + + m_bit_pos = (code_pos + 4) * 8; + decode_slice(code); + slice_failed = m_slice_error; + + // Once the last macroblock has been written the picture is finished. A + // well-formed stream has nothing after it, but a damaged one can leave + // what looks like another slice header lying around, and decoding it + // would re-run rows that are already correct - from a slice position + // that no longer matches, so they come out jumbled. + if (m_macroblock_address >= m_mb_width * m_mb_height - 1) + break; + } + + // And the same for the tail of the picture, as pl_mpeg does. On a clean + // stream the slices cover everything and neither call does anything. When a + // slice dies part way through, this is what keeps the damage invisible: + // without it the missing macroblocks keep whatever the recycled frame + // buffer held - a picture several frames old - and because the result is + // then used as a reference, the wreckage is predicted forward until the + // next intra picture. + conceal_through(m_mb_width * m_mb_height - 1); + + m_work->picture_type = m_picture_type; + m_work->temporal_reference = m_temporal_reference; + m_work->timecode = m_gop_timecode; + + if (m_picture_type == PICTURE_TYPE_B) + { + // B pictures are never used as a reference, so they are shown as soon + // as they are decoded. + m_display_frame = m_work->valid ? m_work : nullptr; + } + else + { + // A new anchor has arrived, so the anchor it follows has reached its + // display time. Rotate: the old future anchor becomes the past one, + // the picture just decoded becomes the future anchor, and the buffer + // the old past anchor used is recycled for the next picture. + frame *const displaced = m_future; + frame *const recycled = m_past; + + m_past = displaced; + m_future = m_work; + m_work = recycled; + + m_display_frame = displaced->valid ? displaced : nullptr; + } + + return true; +} + +//************************************************************************** +// Slice and macroblock layer +//************************************************************************** + +void mpeg1_video_decoder::decode_slice(int vertical_position) +{ + m_quantizer_scale = int(read_bits(5)); + + while (read_bit()) // extra_bit_slice + read_bits(8); + + m_macroblock_address = (vertical_position - 1) * m_mb_width - 1; + m_slice_begin = true; + m_slice_error = false; + + m_dc_predictor[0] = m_dc_predictor[1] = m_dc_predictor[2] = 128; + m_motion_fw_h = m_motion_fw_v = 0; + m_motion_bw_h = m_motion_bw_v = 0; + + do + { + decode_macroblock(); + } while (!m_slice_error && !at_start_code() && m_macroblock_address < m_mb_width * m_mb_height - 1); + +} + +void mpeg1_video_decoder::conceal_through(int last) +{ + const int limit = m_mb_width * m_mb_height; + if (last >= limit) + last = limit - 1; + + if (m_macroblock_address >= last) + return; + + // Concealment is a copy of the co-located macroblock with no motion. A B + // picture keeps whichever directions the last decoded macroblock used, so + // that a gap does not visibly brighten or darken against its neighbours; + // anything else predicts forwards, which for an intra picture means the + // previous anchor. + m_macroblock_intra = false; + m_motion_fw_h = m_motion_fw_v = 0; + m_motion_bw_h = m_motion_bw_v = 0; + if (m_picture_type != PICTURE_TYPE_B || !(m_has_forward || m_has_backward)) + { + m_has_forward = true; + m_has_backward = false; + } + + while (m_macroblock_address < last) + { + m_macroblock_address++; + m_mb_row = m_macroblock_address / m_mb_width; + m_mb_col = m_macroblock_address % m_mb_width; + predict_macroblock(); + } +} + +void mpeg1_video_decoder::decode_macroblock() +{ + int increment = 0; + for (;;) + { + const int t = read_vlc(MACROBLOCK_ADDRESS_INCREMENT); + if (t == 34) // stuffing + continue; + if (t == 35) // escape + { + increment += 33; + continue; + } + increment += t; + break; + } + + // Every macroblock advances the address by at least one, so a zero + // increment means the bit reader is no longer on a macroblock boundary. + if (increment == 0) + { + m_slice_error = true; + return; + } + + if (m_slice_begin) + { + // The first macroblock of a slice counts its increment from the slice's + // own vertical position, not from the macroblock before it, so the + // macroblocks it steps over are not skipped ones - they belong to the + // preceding slice, which may well have already decoded them. Slices are + // free to run past the end of a row, and Monty Python's streams use + // that constantly: a slice announced at row 2 can start five + // macroblocks into it because the row-1 slice spilled over. Treating + // the step as a run of skipped macroblocks re-predicted those five and + // destroyed what the previous slice had correctly decoded, which is + // what showed up on screen as jigsawed blocks. Only advance. + m_slice_begin = false; + m_macroblock_address += increment; + } + else + { + // An increment that would carry the address past the last macroblock + // cannot be right, so the macroblock is dropped without advancing. + // Taking it anyway leaves the address one past the end, which ends the + // slice a macroblock short and silently drops the rest of its row. + if (m_macroblock_address + increment >= m_mb_width * m_mb_height) + { + m_slice_error = true; + return; + } + + if (increment > 1) + { + // Skipped macroblocks. Intra DC prediction restarts either way. In + // a P picture a skipped macroblock is a straight copy with no + // motion; in a B picture it repeats the previous macroblock's + // vectors and prediction mode. + m_dc_predictor[0] = m_dc_predictor[1] = m_dc_predictor[2] = 128; + + if (m_picture_type != PICTURE_TYPE_B) + { + m_motion_fw_h = m_motion_fw_v = 0; + m_has_forward = true; + m_has_backward = false; + } + + m_macroblock_intra = false; + + // An intra picture has no skipped macroblocks, so reaching here at + // all means the stream is damaged. Predicting them from the + // previous anchor conceals the gap; leaving them, as this used to, + // exposes the recycled frame buffer. + for (int i = 1; i < increment; i++) + { + m_macroblock_address++; + m_mb_row = m_macroblock_address / m_mb_width; + m_mb_col = m_macroblock_address % m_mb_width; + predict_macroblock(); + } + } + + m_macroblock_address++; + } + + if (m_macroblock_address >= m_mb_width * m_mb_height) + return; + + m_mb_row = m_macroblock_address / m_mb_width; + m_mb_col = m_macroblock_address % m_mb_width; + + m_macroblock_type = read_vlc(MACROBLOCK_TYPE[m_picture_type]); + if (m_macroblock_type == 0) + { + // No code in the table for this picture type produces zero, so the bit + // reader has drifted. Give the macroblock back to be concealed rather + // than decoding blocks from the wrong offset. + m_slice_error = true; + m_macroblock_address--; + return; + } + + m_macroblock_intra = (m_macroblock_type & MB_INTRA) != 0; + m_has_forward = (m_macroblock_type & MB_FORWARD) != 0; + m_has_backward = (m_macroblock_type & MB_BACKWARD) != 0; + + if (m_macroblock_type & MB_QUANT) + m_quantizer_scale = int(read_bits(5)); + + if (m_macroblock_intra) + { + m_motion_fw_h = m_motion_fw_v = 0; + m_motion_bw_h = m_motion_bw_v = 0; + } + else + { + m_dc_predictor[0] = m_dc_predictor[1] = m_dc_predictor[2] = 128; + + if (m_has_forward) + { + decode_motion_vector(m_motion_fw_h, read_vlc(MOTION), m_forward_r_size, m_forward_f); + decode_motion_vector(m_motion_fw_v, read_vlc(MOTION), m_forward_r_size, m_forward_f); + } + else if (m_picture_type == PICTURE_TYPE_PREDICTIVE) + { + m_motion_fw_h = m_motion_fw_v = 0; + } + + if (m_has_backward) + { + decode_motion_vector(m_motion_bw_h, read_vlc(MOTION), m_backward_r_size, m_backward_f); + decode_motion_vector(m_motion_bw_v, read_vlc(MOTION), m_backward_r_size, m_backward_f); + } + + predict_macroblock(); + } + + const int cbp = m_macroblock_intra + ? 0x3f + : ((m_macroblock_type & MB_PATTERN) ? read_vlc(CODE_BLOCK_PATTERN) : 0); + + for (int block = 0; block < 6; block++) + { + if (cbp & (0x20 >> block)) + decode_block(block); + } + + // A block that could not be read leaves this macroblock part decoded and + // the bit position wrong, so hand it back for concealment too. + if (m_slice_error) + m_macroblock_address--; +} + +void mpeg1_video_decoder::decode_motion_vector(int &dst, int motion, int r_size, int f) +{ + int delta; + if (motion != 0 && f != 1) + { + const int r = int(read_bits(r_size)); + delta = ((std::abs(motion) - 1) << r_size) + r + 1; + if (motion < 0) + delta = -delta; + } + else + { + delta = motion; + } + + dst += delta; + if (dst > (f << 4) - 1) + dst -= f << 5; + else if (dst < ((-f) << 4)) + dst += f << 5; +} + +//************************************************************************** +// Block decoding +//************************************************************************** + +void mpeg1_video_decoder::decode_block(int block) +{ + std::memset(m_block_data, 0, sizeof(m_block_data)); + + int n = 0; + + if (m_macroblock_intra) + { + const int plane = (block > 3) ? (block - 3) : 0; + int predictor = m_dc_predictor[plane]; + + const int dct_size = read_vlc(DCT_SIZE[plane]); + if (dct_size > 0) + { + const int differential = int(read_bits(dct_size)); + if (differential & (1 << (dct_size - 1))) + predictor += differential; + else + predictor += (-1 << dct_size) | (differential + 1); + } + + m_dc_predictor[plane] = predictor; + m_block_data[0] = predictor << 3; // intra DC scaler is 8 + n = 1; + } + + for (;;) + { + const uint16_t coeff = read_vlc_uint(DCT_COEFF); + if (coeff == 0) + { + // No run/level pair packs to zero, so this is a code the table + // cannot produce and the block is being read at the wrong offset. + m_slice_error = true; + break; + } + + int run, level; + + if (coeff == 0x0001 && n > 0) + { + // A leading 1 bit is either end of block (10) or a run of zero + // with level one (11s). + if (read_bit() == 0) + break; + run = 0; + level = read_bit() ? -1 : 1; + } + else if (coeff == DCT_COEFF_ESCAPE) + { + run = int(read_bits(6)); + level = int(read_bits(8)); + if (level == 0) + level = int(read_bits(8)); + else if (level == 128) + level = int(read_bits(8)) - 256; + else if (level > 128) + level -= 256; + } + else + { + run = coeff >> 8; + level = coeff & 0xff; + if (read_bit()) + level = -level; + } + + n += run; + if (n >= 64) + { + // A run that carries past the end of the block cannot be coded, so + // the same applies. + m_slice_error = true; + break; + } + + const int index = ZIG_ZAG[n]; + n++; + + // Dequantise. The result is forced odd, which is what the standard + // calls for to avoid accumulating a DC bias. + int value; + if (m_macroblock_intra) + { + value = (2 * level * m_quantizer_scale * m_intra_quant[index]) / 16; + } + else + { + const int magnitude = std::abs(level); + value = (((2 * magnitude) + 1) * m_quantizer_scale * m_non_intra_quant[index]) / 16; + if (level < 0) + value = -value; + } + + // The standard forces the magnitude odd, but Sign(0) is zero, so a + // coefficient that dequantises to zero must be left alone. + if (value != 0 && (value & 1) == 0) + value -= (value > 0) ? 1 : -1; + + value = std::max(-2048, std::min(2047, value)); + + m_block_data[index] = value; + } + + // The DC-only shortcut in the row pass needs the DC term left alone, so + // run the full transform unconditionally. + idct(m_block_data); + + // Write the result into the current frame, adding to the prediction for + // non-intra macroblocks. + uint8_t *dst; + int stride, x, y; + + if (block < 4) + { + dst = m_work->y.data(); + stride = m_luma_width; + x = m_mb_col * 16 + ((block & 1) * 8); + y = m_mb_row * 16 + ((block >> 1) * 8); + } + else + { + dst = (block == 4) ? m_work->cb.data() : m_work->cr.data(); + stride = m_chroma_width; + x = m_mb_col * 8; + y = m_mb_row * 8; + } + + uint8_t *out = dst + size_t(y) * stride + x; + + if (m_macroblock_intra) + { + for (int row = 0; row < 8; row++) + for (int col = 0; col < 8; col++) + out[size_t(row) * stride + col] = clamp_pixel(m_block_data[row * 8 + col]); + } + else + { + for (int row = 0; row < 8; row++) + for (int col = 0; col < 8; col++) + { + uint8_t &p = out[size_t(row) * stride + col]; + p = clamp_pixel(int(p) + m_block_data[row * 8 + col]); + } + } +} + +void mpeg1_video_decoder::idct(int *block) +{ + for (int i = 0; i < 8; i++) + idct_row(block + i * 8); + for (int i = 0; i < 8; i++) + idct_col(block + i); +} + +//************************************************************************** +// Motion compensation +//************************************************************************** + +void mpeg1_video_decoder::predict_macroblock() +{ + // A P picture predicts from the most recent anchor, which has not yet been + // rotated into the past slot, so it is m_future. A B picture sits between + // the two anchors and uses m_past looking forwards and m_future looking + // backwards. + const frame *const forward_ref = (m_picture_type == PICTURE_TYPE_B) ? m_past : m_future; + + // Test the directions actually used by this macroblock. A leading B + // picture that only predicts backwards can use the new I anchor, while + // one that also needs the missing past anchor cannot be reconstructed. + if (((m_has_forward || !m_has_backward) && !forward_ref->valid) || + (m_has_backward && !m_future->valid)) + { + m_work->valid = false; + return; + } + + if (m_has_forward && m_has_backward) + interpolate_macroblock(*forward_ref, m_motion_fw_h, m_motion_fw_v, *m_future, m_motion_bw_h, m_motion_bw_v); + else if (m_has_backward) + copy_macroblock(*m_future, m_motion_bw_h, m_motion_bw_v, m_full_pel_backward); + else + copy_macroblock(*forward_ref, m_motion_fw_h, m_motion_fw_v, m_full_pel_forward); +} + +namespace { + +// Copies one 8x8 or 16x16 region with half-pel interpolation. +void copy_plane(uint8_t *dst, const uint8_t *src, int stride, int width, int height, + int dst_x, int dst_y, int src_x, int src_y, bool half_x, bool half_y) +{ + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + const uint8_t *p = src + size_t(src_y + row) * stride + src_x + col; + + int value; + if (half_x && half_y) + value = (int(p[0]) + int(p[1]) + int(p[stride]) + int(p[stride + 1]) + 2) / 4; + else if (half_x) + value = (int(p[0]) + int(p[1]) + 1) / 2; + else if (half_y) + value = (int(p[0]) + int(p[stride]) + 1) / 2; + else + value = p[0]; + + dst[size_t(dst_y + row) * stride + dst_x + col] = uint8_t(value); + } + } +} + +// As above, but averages two predictions. +void interpolate_plane(uint8_t *dst, const uint8_t *a, const uint8_t *b, int stride, + int width, int height, int dst_x, int dst_y, + int ax, int ay, bool a_half_x, bool a_half_y, + int bx, int by, bool b_half_x, bool b_half_y) +{ + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + const uint8_t *pa = a + size_t(ay + row) * stride + ax + col; + const uint8_t *pb = b + size_t(by + row) * stride + bx + col; + + int va; + if (a_half_x && a_half_y) + va = (int(pa[0]) + int(pa[1]) + int(pa[stride]) + int(pa[stride + 1]) + 2) / 4; + else if (a_half_x) + va = (int(pa[0]) + int(pa[1]) + 1) / 2; + else if (a_half_y) + va = (int(pa[0]) + int(pa[stride]) + 1) / 2; + else + va = pa[0]; + + int vb; + if (b_half_x && b_half_y) + vb = (int(pb[0]) + int(pb[1]) + int(pb[stride]) + int(pb[stride + 1]) + 2) / 4; + else if (b_half_x) + vb = (int(pb[0]) + int(pb[1]) + 1) / 2; + else if (b_half_y) + vb = (int(pb[0]) + int(pb[stride]) + 1) / 2; + else + vb = pb[0]; + + dst[size_t(dst_y + row) * stride + dst_x + col] = uint8_t((va + vb + 1) / 2); + } + } +} + +// Clamps a source position so that interpolation never reads outside the plane. +inline void clamp_source(int &pos, bool &half, int limit, int size) +{ + if (pos < 0) + { + pos = 0; + half = false; + } + else if (pos + size + (half ? 1 : 0) > limit) + { + pos = std::max(0, limit - size - (half ? 1 : 0)); + if (pos + size + (half ? 1 : 0) > limit) + half = false; + } +} + +} // anonymous namespace + +void mpeg1_video_decoder::copy_macroblock(const frame &src, int motion_h, int motion_v, bool full_pel) +{ + if (src.y.empty()) + return; + + // A full-pel vector counts whole samples, so scale it into the half-pel + // units the interpolation below works in. + const int mh = full_pel ? motion_h * 2 : motion_h; + const int mv = full_pel ? motion_v * 2 : motion_v; + + // Luma + { + int sx = m_mb_col * 16 + (mh >> 1); + int sy = m_mb_row * 16 + (mv >> 1); + bool hx = (mh & 1) != 0; + bool hy = (mv & 1) != 0; + + clamp_source(sx, hx, m_luma_width, 16); + clamp_source(sy, hy, m_luma_height, 16); + + copy_plane(m_work->y.data(), src.y.data(), m_luma_width, 16, 16, + m_mb_col * 16, m_mb_row * 16, sx, sy, hx, hy); + } + + // Chroma: the vectors are halved, truncating toward zero. + { + const int ch = mh / 2; + const int cv = mv / 2; + + int sx = m_mb_col * 8 + (ch >> 1); + int sy = m_mb_row * 8 + (cv >> 1); + bool hx = (ch & 1) != 0; + bool hy = (cv & 1) != 0; + + clamp_source(sx, hx, m_chroma_width, 8); + clamp_source(sy, hy, m_chroma_height, 8); + + copy_plane(m_work->cb.data(), src.cb.data(), m_chroma_width, 8, 8, + m_mb_col * 8, m_mb_row * 8, sx, sy, hx, hy); + copy_plane(m_work->cr.data(), src.cr.data(), m_chroma_width, 8, 8, + m_mb_col * 8, m_mb_row * 8, sx, sy, hx, hy); + } +} + +void mpeg1_video_decoder::interpolate_macroblock(const frame &fwd, int fwd_h, int fwd_v, + const frame &bwd, int bwd_h, int bwd_v) +{ + if (fwd.y.empty() || bwd.y.empty()) + return; + + const int fh = m_full_pel_forward ? fwd_h * 2 : fwd_h; + const int fv = m_full_pel_forward ? fwd_v * 2 : fwd_v; + const int bh = m_full_pel_backward ? bwd_h * 2 : bwd_h; + const int bv = m_full_pel_backward ? bwd_v * 2 : bwd_v; + + // Luma + { + int fx = m_mb_col * 16 + (fh >> 1), fy = m_mb_row * 16 + (fv >> 1); + int bx = m_mb_col * 16 + (bh >> 1), by = m_mb_row * 16 + (bv >> 1); + bool fhx = (fh & 1) != 0, fhy = (fv & 1) != 0; + bool bhx = (bh & 1) != 0, bhy = (bv & 1) != 0; + + clamp_source(fx, fhx, m_luma_width, 16); + clamp_source(fy, fhy, m_luma_height, 16); + clamp_source(bx, bhx, m_luma_width, 16); + clamp_source(by, bhy, m_luma_height, 16); + + interpolate_plane(m_work->y.data(), fwd.y.data(), bwd.y.data(), m_luma_width, + 16, 16, m_mb_col * 16, m_mb_row * 16, fx, fy, fhx, fhy, bx, by, bhx, bhy); + } + + // Chroma + { + const int fch = fh / 2, fcv = fv / 2; + const int bch = bh / 2, bcv = bv / 2; + + int fx = m_mb_col * 8 + (fch >> 1), fy = m_mb_row * 8 + (fcv >> 1); + int bx = m_mb_col * 8 + (bch >> 1), by = m_mb_row * 8 + (bcv >> 1); + bool fhx = (fch & 1) != 0, fhy = (fcv & 1) != 0; + bool bhx = (bch & 1) != 0, bhy = (bcv & 1) != 0; + + clamp_source(fx, fhx, m_chroma_width, 8); + clamp_source(fy, fhy, m_chroma_height, 8); + clamp_source(bx, bhx, m_chroma_width, 8); + clamp_source(by, bhy, m_chroma_height, 8); + + interpolate_plane(m_work->cb.data(), fwd.cb.data(), bwd.cb.data(), m_chroma_width, + 8, 8, m_mb_col * 8, m_mb_row * 8, fx, fy, fhx, fhy, bx, by, bhx, bhy); + interpolate_plane(m_work->cr.data(), fwd.cr.data(), bwd.cr.data(), m_chroma_width, + 8, 8, m_mb_col * 8, m_mb_row * 8, fx, fy, fhx, fhy, bx, by, bhx, bhy); + } +} + +//************************************************************************** +// Output +//************************************************************************** + +const uint8_t *mpeg1_video_decoder::display_y() const +{ + return m_display_frame ? m_display_frame->y.data() : nullptr; +} + +const uint8_t *mpeg1_video_decoder::display_cb() const +{ + return m_display_frame ? m_display_frame->cb.data() : nullptr; +} + +const uint8_t *mpeg1_video_decoder::display_cr() const +{ + return m_display_frame ? m_display_frame->cr.data() : nullptr; +} + +void mpeg1_video_decoder::display_frame_rgb(uint32_t *dst, int stride) const +{ + if (!m_display_frame || m_display_frame->y.empty()) + return; + + const uint8_t *y = m_display_frame->y.data(); + const uint8_t *cb = m_display_frame->cb.data(); + const uint8_t *cr = m_display_frame->cr.data(); + + for (int row = 0; row < m_height; row++) + { + const uint8_t *yr = y + size_t(row) * m_luma_width; + const uint8_t *cbr = cb + size_t(row / 2) * m_chroma_width; + const uint8_t *crr = cr + size_t(row / 2) * m_chroma_width; + uint32_t *out = dst + size_t(row) * stride; + + for (int col = 0; col < m_width; col++) + { + // BT.601, but deliberately *not* expanded to full range: the + // picture is handed to the MCD212 as External Video, and every + // other pixel that chip emits carries the CD-i's 16-235 studio + // swing (see s_4bpp_color[] and the -16/+16 framing in + // mix_lines()). Expanding here would put the cartridge's black at + // 0 while the planes around it sit at 16, and the display window + // would then show up as a darker rectangle against the black the + // title draws around it - most visibly for the couple of fields at + // the start of a clip where the window is open but no picture has + // been shown yet. On hardware the cartridge feeds the same mixer + // and the same DAC as the planes, so its black *is* the CD-i's + // black. + // + // So the luma passes through with its pedestal intact and only the + // chroma coefficients are scaled by 219/255 to match. Y=16 gives + // 16 and Y=235 gives 235; coded values outside the nominal range + // still land outside it, the same way the DYUV decoder's limit + // table lets them. + const int base = (int(yr[col]) << 16) + 32768; + const int u = int(cbr[col / 2]) - 128; + const int v = int(crr[col / 2]) - 128; + + const int r = (base + 89827 * v) >> 16; + const int g = (base - 22049 * u - 45758 * v) >> 16; + const int b = (base + 113538 * u) >> 16; + + out[col] = (uint32_t(clamp_pixel(r)) << 16) + | (uint32_t(clamp_pixel(g)) << 8) + | uint32_t(clamp_pixel(b)); + } + } +} diff --git a/src/mame/machine/mpeg1video.h b/src/mame/machine/mpeg1video.h new file mode 100644 index 00000000..0856f7df --- /dev/null +++ b/src/mame/machine/mpeg1video.h @@ -0,0 +1,327 @@ +// license:BSD-3-Clause +// copyright-holders:vibecodekun +/****************************************************************************** + + ISO/IEC 11172-2 (MPEG-1) video decoder + + Written for the CD-i Digital Video Cartridge, but deliberately free of any + dependency on MAME so that it can be exercised standalone against captured + streams. + + The decoder is fed a demultiplexed video elementary stream through write() + and decodes one picture per call to decode(). Pictures are only decoded + once the whole picture is buffered, so the caller never has to deal with + partially consumed input. + + The variable-length code tables, the zig-zag order, the default quantiser + matrices and the coefficient escape rules are all defined by the standard; + they are transcribed here in the binary-tree form used by pl_mpeg, which + the CDi_MiSTer project also uses for its decoder firmware. pl_mpeg's MIT + license notice is reproduced in mpeg1video.cpp. + +*******************************************************************************/ + +#ifndef MAME_MACHINE_MPEG1VIDEO_H +#define MAME_MACHINE_MPEG1VIDEO_H + +#pragma once + +#include +#include +#include + +namespace mpeg1_detail { + +// A node of a canonical VLC tree: on each bit, either descend to another node +// or emit a value. An index of 0 means the value is final, -1 that the code +// cannot occur. +struct vlc_node +{ + int16_t index; + int16_t value; +}; + +struct vlc_node_u +{ + int16_t index; + uint16_t value; +}; + +} // namespace mpeg1_detail + +class mpeg1_video_decoder +{ +public: + mpeg1_video_decoder(); + + void reset(); + // Discard input and reference pictures while retaining sequence parameters + // for a seek whose new position may not contain another sequence header. + void clear_fifo(); + + // Feed demultiplexed elementary stream bytes. + void write(const uint8_t *data, size_t length); + + // Decode until one picture has been produced. Returns true if a picture + // was decoded, false if more data is needed. + bool decode(); + + bool has_sequence() const { return m_has_sequence; } + int width() const { return m_width; } + int height() const { return m_height; } + + // Frame period of the coded sequence, in 90 kHz ticks, and the raw frame + // rate index from the sequence header. + uint16_t frame_period_90khz() const; + uint8_t frame_rate_index() const { return m_frame_rate_index; } + + // Picture type of the most recently decoded picture (1=I, 2=P, 3=B) and + // its temporal reference. + int picture_type() const { return m_picture_type; } + int temporal_reference() const { return m_temporal_reference; } + // VMPEG packs GOP seconds/pictures into the high word and hours/minutes + // into the low word. Keep this metadata with each reordered picture. + uint32_t decoded_timecode() const { return m_gop_timecode; } + uint32_t display_timecode() const { return m_display_frame ? m_display_frame->timecode : 0; } + + // True once a picture has been decoded that is ready to be shown. Frames + // come out in display order, so an I or P picture is held back until the + // following anchor picture arrives. + bool has_display_frame() const { return m_display_frame != nullptr; } + + // Single-picture/scan mode displays the decoded picture immediately, + // without waiting for the next anchor to reorder an I or P picture. + void display_decoded_frame(); + + // Picture type and temporal reference of the frame currently being shown, + // which because of reordering is not the one most recently decoded. + int display_picture_type() const { return m_display_frame ? m_display_frame->picture_type : 0; } + int display_temporal_reference() const { return m_display_frame ? m_display_frame->temporal_reference : 0; } + + // Converts the current display frame to packed 0x00RRGGBB. dst must have + // room for width() * height() entries; stride is in pixels. + void display_frame_rgb(uint32_t *dst, int stride) const; + + // Raw planar access to the current display frame, for testing. + const uint8_t *display_y() const; + const uint8_t *display_cb() const; + const uint8_t *display_cr() const; + int luma_stride() const { return m_luma_width; } + int chroma_stride() const { return m_chroma_width; } + + // Number of whole pictures currently sitting in the input buffer, which + // is what the cartridge reports to the driver. + int pictures_buffered() const; + + size_t buffered_bytes() const { return m_buffer.size() - m_read_pos; } + + // Clamped so that the save state's fixed-size copy of the buffer can never + // be too small for it. See decoder_state. + void set_buffer_limit(size_t bytes) + { + m_buffer_limit = (bytes > decoder_state::max_buffer) ? decoder_state::max_buffer : bytes; + } + + // True while the buffer holds too much undecoded data to keep accepting + // more, which is what the cartridge reports to the driver through SYS_STS. + // + // This is the live state of the buffer rather than a latch: the hardware + // compares the FIFO level against a high-water mark continuously, so the + // condition goes away again on its own once the decoder has drained enough. + // The mark sits below the limit so that the driver is told to stop while + // there is still room, and nothing has to be dropped. The reference FIFO + // holds 32768 bytes and reports full above 28000 of them; the same fraction + // is used here so that the headroom scales with the buffer. + bool buffer_full() const { return buffered_bytes() > uint64_t(m_buffer_limit) * 28000 / 32768; } + + // Set when a sequence end code has been seen. + bool sequence_ended() const { return m_sequence_ended; } + void clear_sequence_ended() { m_sequence_ended = false; } + + // Everything a sequence header establishes. A stream only carries one + // every so often, so a decoder that is started part way through a clip + // (after a save state is restored, say) has to be told these separately or + // it will discard pictures until the next header comes round, which may be + // never. Kept as plain data so the owning device can save it. + struct sequence_state + { + uint8_t valid = 0; + uint16_t width = 0; + uint16_t height = 0; + uint8_t frame_rate_index = 0; + uint8_t intra_quant[64] = { 0 }; + uint8_t non_intra_quant[64] = { 0 }; + }; + + void get_sequence_state(sequence_state &state) const; + void set_sequence_state(const sequence_state &state); + + // Everything else a save state has to carry. + // + // The bit reader, picture, slice and macroblock fields are deliberately + // absent. decode() only ever returns with the bit position parked on a + // start code - every early exit rewinds to one - so a picture is never half + // decoded between calls and none of that state is live across a save. + // + // What cannot be reconstructed is the pair of reference pictures. Without + // them the first P and B pictures after a load predict from whatever the + // buffers happened to hold, which shows as a few frames of smeared blocks, + // so they ride along in full. + struct decoder_state + { + // The input buffer is capped at the largest the decoder will accept + // (set_buffer_limit() clamps to this), so the copy can never truncate. + // The frame cap covers the biggest picture a Green Book stream carries, + // 384x280 rounded up to whole macroblocks. A larger picture still saves + // and loads correctly, just without its reference pictures: frames_saved + // comes back 0 and the stream resyncs on the next intra picture, which + // is what the hardware does across a seek anyway. + static constexpr size_t max_buffer = 512 * 1024; + static constexpr size_t max_luma = 384 * 288; + static constexpr size_t max_chroma = 192 * 144; + + // Undecoded input, rebased so that the read position is byte 0. + uint32_t buffer_size = 0; + uint32_t bit_pos = 0; // bit offset into buffer[] + uint8_t buffer[max_buffer] = { 0 }; + + uint8_t sequence_ended = 0; + // Low three bits are picture type; the remaining bits carry the GOP + // timecode shifted left by three. Its highest bit is bit 27, so this + // fits a positive int32_t. Legacy states have zero in the unused bits + // and recover timecodes when the next GOP header arrives. This retains + // the existing save layout and all input-buffer capacity. + int32_t picture_type = 0; + int32_t temporal_reference = 0; + + // Frame store. The four working pointers are saved as indices into + // frames[], with -1 standing in for a null display frame. + uint8_t frames_saved = 0; + int32_t past_index = 1; + int32_t future_index = 2; + int32_t work_index = 0; + int32_t display_index = -1; + uint8_t frame_valid[3] = { 0, 0, 0 }; + int32_t frame_picture_type[3] = { 0, 0, 0 }; // same packed metadata + int32_t frame_temporal_reference[3] = { 0, 0, 0 }; + uint8_t frame_y[3][max_luma] = { { 0 } }; + uint8_t frame_cb[3][max_chroma] = { { 0 } }; + uint8_t frame_cr[3][max_chroma] = { { 0 } }; + }; + + // Must be called after set_sequence_state(), which reallocates and clears + // the frame store whenever the picture size changes. + void get_decoder_state(decoder_state &state) const; + void set_decoder_state(const decoder_state &state); + +private: + struct frame + { + std::vector y, cb, cr; + int temporal_reference = 0; + uint32_t timecode = 0; + int picture_type = 0; + bool valid = false; + }; + + using vlc_node = mpeg1_detail::vlc_node; + using vlc_node_u = mpeg1_detail::vlc_node_u; + + // --- bit reading over the input buffer --- + bool have_bits(size_t count) const; + uint32_t read_bits(int count); + int read_bit(); + void align_to_byte(); + int read_vlc(const vlc_node *table); + uint16_t read_vlc_uint(const vlc_node_u *table); + + // Finds the byte offset of the next 00 00 01 start code prefix at or after + // `from`, or SIZE_MAX. Only searches whole bytes. + size_t find_start_code(size_t from) const; + // True when the next 23 bits are zero, i.e. a start code follows. + bool at_start_code() const; + + void discard_consumed(); + + bool decode_sequence_header(); + void decode_gop_header(); + bool decode_picture(); + void decode_slice(int vertical_position); + void decode_macroblock(); + void decode_block(int block); + void decode_motion_vector(int &dst, int motion, int r_size, int f); + + // Stands in every macroblock from the one after the last that was written + // up to and including `last`, by predicting it from the reference picture. + void conceal_through(int last); + + void predict_macroblock(); + void copy_macroblock(const frame &src, int motion_h, int motion_v, bool full_pel); + void interpolate_macroblock(const frame &fwd, int fwd_h, int fwd_v, const frame &bwd, int bwd_h, int bwd_v); + + void allocate_frames(); + void idct(int *block); + + // --- input buffer --- + std::vector m_buffer; + size_t m_read_pos = 0; // byte offset of the next unconsumed byte + size_t m_bit_pos = 0; // absolute bit offset into m_buffer + size_t m_buffer_limit = 512 * 1024; + + // --- sequence state --- + bool m_has_sequence = false; + bool m_sequence_ended = false; + int m_width = 0, m_height = 0; + int m_mb_width = 0, m_mb_height = 0; + int m_luma_width = 0, m_luma_height = 0; + int m_chroma_width = 0, m_chroma_height = 0; + uint8_t m_frame_rate_index = 0; + uint8_t m_intra_quant[64]; + uint8_t m_non_intra_quant[64]; + + // --- picture state --- + int m_picture_type = 0; + int m_temporal_reference = 0; + uint32_t m_gop_timecode = 0; + int m_forward_f_code = 0, m_backward_f_code = 0; + int m_forward_r_size = 0, m_backward_r_size = 0; + int m_forward_f = 0, m_backward_f = 0; + bool m_full_pel_forward = false, m_full_pel_backward = false; + + // --- slice / macroblock state --- + int m_quantizer_scale = 0; + int m_macroblock_address = 0; + // Set while the next macroblock is the first of a slice, whose address + // increment is measured from the slice's vertical position rather than from + // the macroblock before it. See decode_macroblock(). + bool m_slice_begin = false; + // Set when a symbol turns up that cannot occur in a well-formed stream, + // which means the bit reader has lost the slice. The rest of the slice is + // abandoned and its macroblocks are concealed instead of being decoded from + // what is no longer the right bit position. Purely per-slice, so it is not + // part of decoder_state and does not affect the save state size. + bool m_slice_error = false; + int m_mb_row = 0, m_mb_col = 0; + int m_macroblock_type = 0; + bool m_macroblock_intra = false; + int m_dc_predictor[3] = { 0, 0, 0 }; + int m_motion_fw_h = 0, m_motion_fw_v = 0; + int m_motion_bw_h = 0, m_motion_bw_v = 0; + bool m_has_forward = false, m_has_backward = false; + int m_block_data[64]; + + // --- frames --- + // + // Pictures arrive in decode order but must be shown in display order, so + // the two anchor pictures either side of any B pictures are both kept. + // m_past is the older anchor, m_future the newer one, and m_work is the + // picture currently being decoded. An anchor only becomes displayable once + // the next anchor has been decoded. + frame m_frames[3]; + frame *m_past = nullptr; + frame *m_future = nullptr; + frame *m_work = nullptr; + frame *m_display_frame = nullptr; +}; + +#endif // MAME_MACHINE_MPEG1VIDEO_H diff --git a/src/mame/video/mcd212.cpp b/src/mame/video/mcd212.cpp index def10e3c..a7206196 100644 --- a/src/mame/video/mcd212.cpp +++ b/src/mame/video/mcd212.cpp @@ -331,10 +331,37 @@ int mcd212_device::get_border_width() return width; } -uint32_t mcd212_device::get_backdrop_plane() +// Samples the Digital Video Cartridge's picture across the line about to be +// drawn. It is read up-front rather than from get_backdrop_plane() because the +// plane decoders need to know where the cartridge's picture falls: see the +// region-flag handling in process_vsr(). +void mcd212_device::update_ext_video_line() +{ + m_ext_active = BIT(m_image_coding_method, ICM_EV_BIT) && !m_ext_video_cb.isnull(); + if (!m_ext_active) + return; + + const int width = get_screen_width(); + for (int x = 0; x < width; x++) + { + const uint32_t pixel = m_ext_video_cb(x, m_backdrop_scanline); + m_ext_present[x] = (pixel & 0xff000000) != 0; + m_ext_pixel[x] = pixel & 0x00ffffff; + } +} + +uint32_t mcd212_device::get_backdrop_plane(int x) { if (BIT(m_image_coding_method, ICM_EV_BIT)) - return 0; // External Video Background. Default to Black since there is no DVC. + { + // External Video Background: this is where a Digital Video Cartridge + // shows through. With no cartridge, or outside of its display window, + // the backdrop is black - the CD-i's black, the same level the border + // and a black plane pixel come out at, not zero. + if (m_ext_active && m_ext_present[x]) + return m_ext_pixel[x]; + return CDI_BLACK; + } else { // Only the low nibble of the register is the colour - intensity, R, G, @@ -571,6 +598,22 @@ void mcd212_device::process_vsr(uint32_t *pixels, bool *transparent) const bool is_dyuv_rgb = (icm == ICM_DYUV) || ((icm == ICM_RGB555) && (Path == 1)); // DYUV and RGB do not have access to color key. const bool use_color_key = !is_dyuv_rgb && ((tp_ctrl_type == TCR_KEY) || (tp_ctrl_type == TCR_MF0_KEY1) || (tp_ctrl_type == TCR_MF1_KEY1)); + // With External Video selected, a region flag marks out where a Digital + // Video Cartridge is keyed in, rather than keying the plane away outright. + // A title that hands the whole line to the cartridge and then relies on the + // cartridge only driving its own display window - as Mutant Rampage does for + // the border it draws around its MPEG window - needs the plane back outside + // that window, or the border disappears and the video is left floating on + // black. Note the test is on the window being open, not on a picture being + // available: the window keys the plane away just the same between clips, + // which is why a stopped or skipped clip leaves a black void rather than + // revealing the artwork the title parks behind it. + const bool region_keys_ext = m_ext_active && use_matte_flag; + const auto matte_transparent = [&](uint32_t px) + { + return (matte_flags[px] == tp_check_parity) && (!region_keys_ext || m_ext_present[px]); + }; + // A plane whose coding method is OFF is still a plane: it drives black into // the mixer, and whether that black is keyed away is decided by the plane's // own transparency control, exactly as it would be for a real picture. Only @@ -584,7 +627,7 @@ void mcd212_device::process_vsr(uint32_t *pixels, bool *transparent) { std::fill_n(pixels, width, s_4bpp_color[0]); for (int x = 0; x < width; x++) - transparent[x] = tp_always || (use_matte_flag && (matte_flags[x] == tp_check_parity)); + transparent[x] = tp_always || (use_matte_flag && matte_transparent(x)); return; } @@ -633,10 +676,10 @@ void mcd212_device::process_vsr(uint32_t *pixels, bool *transparent) pixels[x + 1] = color0; pixels[x + 2] = color1; pixels[x + 3] = color1; - transparent[x ] = tp_always || (use_matte_flag && (matte_flags[x ] == tp_check_parity)); - transparent[x + 1] = tp_always || (use_matte_flag && (matte_flags[x + 1] == tp_check_parity)); - transparent[x + 2] = tp_always || (use_matte_flag && (matte_flags[x + 2] == tp_check_parity)); - transparent[x + 3] = tp_always || (use_matte_flag && (matte_flags[x + 3] == tp_check_parity)); + transparent[x ] = tp_always || (use_matte_flag && matte_transparent(x )); + transparent[x + 1] = tp_always || (use_matte_flag && matte_transparent(x + 1)); + transparent[x + 2] = tp_always || (use_matte_flag && matte_transparent(x + 2)); + transparent[x + 3] = tp_always || (use_matte_flag && matte_transparent(x + 3)); x += 4; } else @@ -676,8 +719,8 @@ void mcd212_device::process_vsr(uint32_t *pixels, bool *transparent) { pixels[rl_index ] = color0; pixels[rl_index + 1] = color1; - transparent[rl_index ] = tp_always || rgb_tp_bit || (use_color_key && color_match0) || (use_matte_flag && (matte_flags[rl_index ] == tp_check_parity)); - transparent[rl_index + 1] = tp_always || rgb_tp_bit || (use_color_key && color_match1) || (use_matte_flag && (matte_flags[rl_index + 1] == tp_check_parity)); + transparent[rl_index ] = tp_always || rgb_tp_bit || (use_color_key && color_match0) || (use_matte_flag && matte_transparent(rl_index )); + transparent[rl_index + 1] = tp_always || rgb_tp_bit || (use_color_key && color_match1) || (use_matte_flag && matte_transparent(rl_index + 1)); } x = end; } @@ -715,7 +758,7 @@ void mcd212_device::mix_lines(uint32_t *plane_a, bool *transparent_a, uint32_t * { if (transparent_a[x] && transparent_b[x]) { - out[x] = get_backdrop_plane(); + out[x] = get_backdrop_plane(x); continue; } uint32_t plane_a_cur = MosaicA ? plane_a[x - (x % mosaic_count_a)] : plane_a[x]; @@ -1036,6 +1079,7 @@ uint32_t mcd212_device::screen_update(screen_device &screen, bitmap_rgb32 &bitma bool transparent_b[768]; int scanline = screen.vpos(); + m_backdrop_scanline = scanline - m_ica_height; // Process VSR and mix if we're in the visible region if (scanline >= m_ica_height) @@ -1064,6 +1108,8 @@ uint32_t mcd212_device::screen_update(screen_device &screen, bitmap_rgb32 &bitma out += 24; } + update_ext_video_line(); + process_vsr<0>(plane_a, transparent_a); process_vsr<1>(plane_b, transparent_b); @@ -1241,6 +1287,7 @@ mcd212_device::mcd212_device(const machine_config &mconfig, const char *tag, dev : device_t(mconfig, MCD212, tag, owner, clock) , device_video_interface(mconfig, *this) , m_int_callback(*this) + , m_ext_video_cb(*this) , m_planea(*this, finder_base::DUMMY_TAG) , m_planeb(*this, finder_base::DUMMY_TAG) { @@ -1255,6 +1302,7 @@ mcd212_device::mcd212_device(const machine_config &mconfig, const char *tag, dev void mcd212_device::device_resolve_objects() { m_int_callback.resolve_safe(); + m_ext_video_cb.resolve(); } //------------------------------------------------- diff --git a/src/mame/video/mcd212.h b/src/mame/video/mcd212.h index 94ef9a41..6c80946d 100644 --- a/src/mame/video/mcd212.h +++ b/src/mame/video/mcd212.h @@ -47,6 +47,23 @@ public: auto int_callback() { return m_int_callback.bind(); } + // The CD-i's black. Everything this chip emits carries the 16-235 studio + // swing: the 4bpp constants below run 0x10 to 0xe6, and mix_lines() takes + // the pedestal off each plane before weighting it and puts it back + // afterwards. Anything feeding pixels in from outside - the External Video + // backdrop - has to speak the same units, or its black reads as a darker + // hole in the picture. + static constexpr uint32_t CDI_BLACK = 0x00101010; + + // Supplies the External Video backdrop, i.e. the picture coming from a + // Digital Video Cartridge. Called with the active-area pixel position; + // returns 0 when the cartridge's display window does not cover that pixel, + // or the colour with the alpha byte set when it does. A covered pixel with + // no picture behind it comes back CDI_BLACK rather than unoccupied, so that + // the window still keys the CD-i plane away between clips. + typedef device_delegate ext_video_delegate; + template void set_ext_video_callback(T &&... args) { m_ext_video_cb.set(std::forward(args)...); } + uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); void map(address_map &map) ATTR_COLD; @@ -231,6 +248,9 @@ protected: // interrupt callbacks devcb_write_line m_int_callback; + // External Video source (Digital Video Cartridge) + ext_video_delegate m_ext_video_cb; + required_shared_ptr m_planea; required_shared_ptr m_planeb; @@ -259,7 +279,10 @@ protected: int get_screen_width(); int get_border_width(); - uint32_t get_backdrop_plane(); + uint32_t get_backdrop_plane(int x); + void update_ext_video_line(); + + int m_backdrop_scanline = 0; // The address the display is currently fetching each plane from. // @@ -277,6 +300,14 @@ protected: // leaving it out keeps existing save states loadable. uint32_t m_vsr_pos[2] = { 0, 0 }; + // The external-video picture for the scanline being drawn, sampled once + // before the planes are decoded. m_ext_present marks where the cartridge's + // display window is open, not where it happens to have a picture. Scratch + // for one line, so deliberately not part of the save state. + uint32_t m_ext_pixel[768]{}; + bool m_ext_present[768]{}; + bool m_ext_active = false; + template void set_vsr(uint32_t value); template uint32_t get_vsr();