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();