{"id":13759544,"url":"https://github.com/afska/gba-remote-play","last_synced_at":"2026-01-18T14:44:37.559Z","repository":{"id":46026521,"uuid":"369384867","full_name":"afska/gba-remote-play","owner":"afska","description":"📡 Stream Raspberry Pi games to a GBA via Link Cable.","archived":false,"fork":false,"pushed_at":"2024-04-20T10:22:48.000Z","size":1731,"stargazers_count":419,"open_issues_count":0,"forks_count":9,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-05-21T02:45:26.265Z","etag":null,"topics":["advance","cable","gameboy","gba","link","link-cable","netplay","pi","play","raspberry","raspberry-pi","remote","rpi","serial","spi"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/afska.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-21T01:49:10.000Z","updated_at":"2024-06-24T12:16:25.401Z","dependencies_parsed_at":"2024-02-12T12:28:28.988Z","dependency_job_id":"ddf1dbab-eebf-4cd1-9d1b-6a34a16cc006","html_url":"https://github.com/afska/gba-remote-play","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afska%2Fgba-remote-play","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afska%2Fgba-remote-play/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afska%2Fgba-remote-play/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/afska%2Fgba-remote-play/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/afska","download_url":"https://codeload.github.com/afska/gba-remote-play/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224941030,"owners_count":17395811,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["advance","cable","gameboy","gba","link","link-cable","netplay","pi","play","raspberry","raspberry-pi","remote","rpi","serial","spi"],"created_at":"2024-08-03T13:00:55.145Z","updated_at":"2026-01-18T14:44:37.507Z","avatar_url":"https://github.com/afska.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"# gba-remote-play\n\n[![demo](https://user-images.githubusercontent.com/1631752/142566072-26e19a9c-e24b-42c7-97f1-847196a1ed5c.png)](https://www.youtube.com/watch?v=PoRmuKUQRY0)\n\nThis software streams games from a Raspberry Pi to a Game Boy Advance, through its Link Port. Video and audio are compressed and sent in real time to the GBA, while the latter responds with its current input, allowing users to play games of any platform by using the GBA (hence, Remote Play).\n\n**Features:**\n\n- Plays any game using [RetroPie](https://retropie.org.uk/) on the GBA!\n- _120x80_ pixels of power!\n- ~60fps using the default display mode\n- Retro scanlines 😎\n- More _pixels of power_ on overclocked GBAs\n- Experimental audio support!\n- ~~Crashes on the GB Micro! _(yep, that's a feature)_~~\n\n\u003e \u003cimg alt=\"rlabs\" width=\"16\" height=\"16\" src=\"https://user-images.githubusercontent.com/1631752/116227197-400d2380-a72a-11eb-9e7b-389aae76f13e.png\" /\u003e Created by [[r]labs](https://r-labs.io).\n\n**Check out my other GBA projects!**\n\n- [piuGBA](https://github.com/afska/piugba/): A PIU emulator 💃↙️↖️⏹↗️↘️🕺\n- [gba-link-connection](https://github.com/afska/gba-link-connection): A multiplayer library 🎮🔗🎮\n\n# Index\n\n- [Demos](#demos)\n- [How it works](#how-it-works)\n- [Setup](#setup)\n- [GBA Jam 2021](#gba-jam-2021)\n- [Credits](#credits)\n\n# Demos\n\nhttps://user-images.githubusercontent.com/1631752/142571402-27ff4952-352f-41c4-888b-95575ea91b53.mp4\n\nhttps://user-images.githubusercontent.com/1631752/125162840-7cb0be80-e160-11eb-8c10-cf8a09f7af2b.mp4\n\nhttps://user-images.githubusercontent.com/1631752/125162670-a0273980-e15f-11eb-80fb-fee16ae5a6f7.mp4\n\n# How it works\n\n\u003e :warning: This section will talk about implementation details. For setup instructions, scroll down to [Setup](#setup)! :warning:\n\nBasically, there are two programs:\n\n- On the GBA, a ROM that receives data.\n- On the RPI, a program that collects and sends data.\n\nThe ROM is sent to the GBA by using the multiboot protocol, which allows small programs to be sent via [Link Cable](https://en.wikipedia.org/wiki/Game_Link_Cable). No cartridge is required.\n\n## Serial communication\n\nCommunication is done through a GBA's Link Cable, soldered to the Raspberry Pi's pins.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eGBA Link Cable's pinout\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/124884342-8ee7fc80-dfa8-11eb-9bd2-4741a4b9acc6.png\"\u003e\n\u003c/p\u003e\n\n### Communication modes\n\nThe GBA supports [several serial communication modes](https://problemkaputt.de/gbatek.htm#gbacommunicationports). Depending on which _mode_ you use, the pins will behave differently. The most common ones are:\n\n- **Normal Mode**: It's essentially [SPI mode 3](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface), but they call it \"Normal mode\" here. The transfer rate can be either _256Kbit/s_ or _2Mbit/s_, and packets can be 8-bit or 32-bit.\n- **Multiplayer Mode**: What games normally use for multiplayer with up to 4 simultaneous GBAs. The maximum transfer rate is _115200bps_ and packets are always 16-bit.\n- **General Purpose Input/Output**: Classic [GPIO](https://en.wikipedia.org/wiki/General-purpose_input/output), used for controlling LEDs, rumble motors, and that kind of stuff.\n\nTo have a decent frame rate, this project uses the maximum available speed: that's **Normal Mode at 2Mbps**, with 32-bit transfers.\n\n### Normal Mode / SPI\n\nSPI is a synchronous protocol supported by hardware in many devices, that allows full-duplex transmission. There's a master and a slave, and when the master issues a clock cycle, the two devices send data to each other (one bit at a time).\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eSPI cycle\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/124885912-2437c080-dfaa-11eb-9eac-006ba64429b8.png\"\u003e\n\u003c/p\u003e\n\n\u003e This is what happens on an SPI cycle. Both devices use shift registers to move bits of data circularly. You can read more about the data transmission protocol [here](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#Data_transmission).\n\nThe GBA can work both as master or as slave, but the Raspberry Pi only works as master. So, the Raspberry controls the clock.\n\nAs for the connection, only 4 pins are required for the transmission: **CLK** (clock), **MOSI** (master out, slave in), **MISO** (master in, slave out), and **GND** (ground).\n\n- On the GBA, these are Pin 5 (_SC_), Pin 3 (_SI_), Pin 2 (_SO_), and Pin 6 (_GND_).\n- On the RPI, these are GPIO 11 (_SPI0 SCLK_), GPIO 10 (_SPI0 MOSI_), GPIO 9 (_SPI0 MISO_), and one of its multiple GNDs.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eGBA \u003c-\u003e Raspberry Pi connection diagram\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/124893645-497bfd00-dfb1-11eb-89d7-34fa7b3f77c8.png\"\u003e\n\u003c/p\u003e\n\nSome peculiarities about GBA's Normal Mode:\n\n- When linking two GBAs, you need to use a GBC Link Cable. If you use a GBA one, the communication will be one-way: the slave will receive data but the master will receive zeroes.\n- Communication at 2Mbps is only reliable when using very short wires, as it's intended for special expansion hardware.\n\n**Related code:**\n\n- [SPIMaster](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/SPIMaster.h#L24) on the Raspberry Pi\n- [SPISlave](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/SPISlave.h#L15) on the GBA\n- [SPIMaster](https://github.com/afska/gba-remote-play/blob/gba-jam/gba/src/gbajam/SPIMaster.h#L8) on the GBA (used for the [GBA Jam demo](#gba-jam-2021))\n\n### Reaching the maximum speed\n\nIn my tests with a Raspberry Pi 3, the maximum transfer rates I was able to achieve were:\n\n- **Bidirectional**: 1.6Mbps. From here, the Raspberry Pi starts receiving garbage from the GBA.\n- **One-way**: 2.6Mbps. Crank this up, and you'll have corrupted packets.\n- **One-way, on an overclocked GBA**: 4.8Mbps, using a 12Mhz crystal oscillator instead of the default one (4.194Mhz).\n\nOne-way transfers are fine in this case, because we only care about input and some sync packets from the GBA. That means that the code is constantly switching between two frequencies depending on whether it needs a response or not.\nIn all cases the Raspberry Pi has to wait a small number of microseconds to let the poor GBA's CPU rest.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eSpeed benchmark\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125121356-314bd100-e0ca-11eb-950d-6e848a53891f.png\"\u003e\n\u003c/p\u003e\n\n\u003e The first dot means 40000 packets/second and each extra dot adds 5000 more. At maximum speed, they should be all green. The one at the right indicates if we're free of corrupted packets. If it's red, adjust!\n\n**Related code:**\n\n- [Delay before each transfer](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/SPIMaster.h#L80)\n- [GBA benchmark code](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/Benchmark.h#L54)\n- [RPI benchmark code](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/Benchmark.h#L31)\n- [SPI config](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/out/config.cfg#L1)\n\n### MISO waits\n\nIn classic SPI, the master blindly issues clock cycles and it's responsibility of the slave to catch up and process all packets on time. But here, sometimes the GBA is very busy doing things like putting pixels on screen or whatever it has to do, so it needs a way to tell the master to stop.\n\nAs recommended in the GBA manual, the slave can put MISO on HIGH when it's unable to receive, and master can read its value as a GPIO input pin and wait to send until it's LOW.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003ePls don't send me anything\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125120423-d2398c80-e0c8-11eb-8f67-62f2b0089949.gif\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [Disable transfers (slave)](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/SPISlave.h#L46)\n- [MISO wait (master)](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/SPIMaster.h#L83)\n\n## Video\n\n### Reading screen pixels\n\nFirst, we need to configure [Raspbian](https://en.wikipedia.org/wiki/Raspberry_Pi_OS) to use a frame buffer size that matches the GBA's resolution: **240x160**. There are two properties called `framebuffer_width` and `framebuffer_height` inside `/boot/config.txt` that let us change this.\n\nLinux can provide all the pixel data shown on the screen (frame buffers) in devfiles like `/dev/fb0`. That works well when using desktop applications, but not for fullscreen games that use OpenGL -for example-, since they talk directly to the Raspberry Pi's GPU. So, to gather the colors no matter what application is running, we use the _dispmanx API_ (calling `vc_dispmanx_snapshot(...)` once per frame), which provides us a nice [RGBA32](https://en.wikipedia.org/wiki/RGBA_color_model#ARGB32) pixel matrix with all the screen data.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eHere's one of the many ways of reading the frame buffer wrong\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125160284-77e50e00-e152-11eb-8d83-d94206e13ca5.jpg\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [FrameBuffer](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/FrameBuffer.h#L17)\n\n### Drawing on the GBA screen\n\nInstead of _RGBA32_, the GBA understands _RGB555_ (or _15bpp color_), which means 5 bits for red, 5 for green, and 5 for blue with no alpha channel. As it's a little-endian system, first one is red.\n\nTo draw those colors on the screen, it supports 3 [different bitmap modes](https://www.coranac.com/tonc/text/bitmaps.htm). For this project, I used _mode 4_, where each pixel is an **8-bit** reference to a palette of **256** 15bpp colors. The only consideration to have when using mode 4 is that VRAM doesn't support 8-bit writes, so you have to read first what's on the address to rewrite the complete halfword/word.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003e15bpp color representation\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125148694-8c9db380-e10a-11eb-96f9-43646f638491.png\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [GBA writing a pixel in mode 4](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/Utils.h#L32)\n\n### Color quantization\n\nSo, the Raspberry Pi has to [quantize](https://en.wikipedia.org/wiki/Color_quantization) every frame to a 256 colors palette. In an initial iteration, I was using a quantization library that generated the most optimal palette for each frame. Though that's the best regarding image quality, it was too slow. The implemented solution ended up using a fixed palette ([this one](https://en.wikipedia.org/wiki/List_of_software_palettes#6-8-5_levels_RGB) in particular), and approximate every color to a byte referencing palette's colors.\n\n\u003ctable border=\"0\"\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n      \u003cp align=\"center\"\u003e\n        \u003ci\u003eOriginal image\u003c/i\u003e\n        \u003cbr\u003e\n        \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125025069-60295f00-e058-11eb-930c-3c1b2c80d5ee.png\"\u003e\n      \u003c/p\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      \u003cp align=\"center\"\u003e\n        \u003ci\u003eQuantized image\u003c/i\u003e\n        \u003cbr\u003e\n        \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125025072-63bce600-e058-11eb-8f44-b33a782549fc.png\"\u003e\n      \u003c/p\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nTo approximate colors faster, when running the code for the first time, it creates a 16MB [lookup table](https://en.wikipedia.org/wiki/Lookup_table) called \"palette cache\" with all the possible color convertions. It's 16MB because there are 2^24 possible colors and each palette index is one byte.\n\n**Related code:**\n\n- [15bpp palette on GBA](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/Palette.h#L6)\n- [24bpp palette on RPI](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/Palette.h#L12)\n- [Closest color math](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/Palette.h#L52)\n- [Palette cache](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/Palette.h#L68)\n- [JS code used to construct the table](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/Palette.h#L111)\n\n### Scaling\n\nThe frame buffer is _240x160_ but what's sent to the GBA is configurable, so if you prefer a killer frame rate over detail you can send _120x80_ and use the [mosaic effect](https://www.coranac.com/tonc/text/gfx.htm#sec-mos) to scale the image so it fills the entire screen. Or, if you like old [CRT](https://en.wikipedia.org/wiki/Cathode-ray_tube)s, you could send _240x80_ and draw artificial scanlines between each actual line.\n\nThe Raspberry Pi discards each pixel that is not a multiple of the drawing scale. For example, if you use a 2x width scale factor, it will discard odd pixels and the resulting width will be _120_ instead of _240_.\n\nAt the time of rendering, you have to take this into account because GBA's _mode 4_ expects a _240x160_ pixel matrix. If you give it less, you'd only fill a part of the screen.\n\n\u003ctable border=\"0\"\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n      \u003cp align=\"center\"\u003e\n        \u003ci\u003eNo scaling\u003c/i\u003e\n        \u003cbr\u003e\n        \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125113283-d6f94300-e0be-11eb-9274-e6ef407e566f.gif\"\u003e\n      \u003c/p\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      \u003cp align=\"center\"\u003e\n        \u003ci\u003e2x mosaic\u003c/i\u003e\n        \u003cbr\u003e\n        \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125113332-ea0c1300-e0be-11eb-9ee0-8fd6e0d4eb49.gif\"\u003e\n      \u003c/p\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      \u003cp align=\"center\"\u003e\n        \u003ci\u003eScanlines\u003c/i\u003e\n        \u003cbr\u003e\n        \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125113327-e8424f80-e0be-11eb-8dc5-9d057d7d39fc.gif\"\u003e\n      \u003c/p\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003e Here are 3 ways of scaling the same _120x80_ clip.\n\n**Related code:**\n\n- [RPI ignoring pixels](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/GBARemotePlay.h#L303)\n- [GBA setting up mosaic](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L79)\n- [GBA selecting the draw cursor](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L180)\n\n### Image compression\n\n#### Temporal diffs\n\nThe code only sends the pixels that changed since the previous frame, and what \"changed\" means can be configured: there's a \"compression\" (diff threshold) parameter in the runtime configuration that controls how far should be a color to the previous one in order to refresh it.\n\nAt the compression stage, it creates a [bit array](https://en.wikipedia.org/wiki/Bit_array) where 1 means that a pixel did change, and 0 that it didn't. Then, it sends that array + the pixels with '1'.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eExample of a 13x1 diff array\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125102399-9b0bb100-e0b1-11eb-9f71-32cbe1b16734.png\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [ImageDiffRLECompressor](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/ImageDiffRLECompressor.h#L8)\n- [Bitarray transfer](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/GBARemotePlay.h#L211)\n- [Diff decompression](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L239)\n- [Diff threshold possible values](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/Protocol.h#L93)\n\n#### Run-length encoding\n\nThe resulting buffer of the temporal compression is run-length encoded.\n\nWhen using paletted images, it's highly likely that there are consecutive pixels with the same color. Or, for example, during screen transitions where all pixels are black, instead of sending N black pixels (N bytes) we can send 1 byte for N and then the black color (2 bytes). That's [RLE](https://en.wikipedia.org/wiki/Run-length_encoding).\n\nHowever, RLE doesn't always make things better: it can sometimes produce a longer buffer than the original one because it has to add the \"count\" byte for every payload byte. For that reason, the encoding is made of two stages, and it only applies RLE if it helps compressing the data. Then, the frame's metadata stores a bit that represents if the payload is RLE'd or not.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eEncoding the compressed buffer\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125102404-9cd57480-e0b1-11eb-9490-3622d2161991.png\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [RLE bit in frame's metadata](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L131)\n- [RLEncoding](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/GBARemotePlay.h#L265)\n- [RLDecoding](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L184)\n\n#### Trimming the diffs\n\nFor a render resolution of _120x80_, the bit array would be _120x80/8 = 1200bytes_. That's a lot to transfer every frame, so it only sends the chunk from the first '1' to the last '1', but of course in 32-bit packets.\n\n```\n                                                                v startPacket                   v endPacket\nPACKET 0                        PACKET 1                        PACKET 2                        PACKET 3                        PACKET 4                        PACKET 5\nBYTE 0  BYTE 1  BYTE 2  BYTE 3  BYTE 4  BYTE 5  BYTE 6  BYTE 7  BYTE 8  BYTE 9  BYTE 10 BYTE 11 BYTE 12 BYTE 13 BYTE 14 BYTE 15 BYTE 16 BYTE 17 BYTE 18 BYTE 19 BYTE 20 BYTE 21 BYTE 22 BYTE 23\n00000000000000000000000000000000000000000000000000000000000000000000000000100100110010000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n                                                                          ^ startPixel           ^endPixel\n                                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ packetsToSend\n\n// 24x1 screen\nmaxPackets = 6\nstartPixel = 74\nstartPacket = startPixel / 8 / 4 = 2\nendPixel = 97\nendPacket = endPixel / 8 / 4 = 3\ntotalPackets = endPacket - startPacket + 1;\n```\n\n## Input\n\nEach frame, the GBA sends its pressed keys to the Raspberry Pi. It does so by reading [REG_KEYINPUT](https://www.coranac.com/tonc/text/keys.htm) and transferring it on the initial metadata exchange.\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eBits are set when a key is **not** pressed. Weird design!\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125149055-fd45cf80-e10c-11eb-8634-a1dbe27db9e1.png\"\u003e\n\u003c/p\u003e\n\nIn Linux, there's `/dev/uinput` which lets user space processes create virtual devices and update its state. You can create your virtual gamepad however you like, for example, adding analog sticks and then mapping GBA's D-pad to analog values.\n\nThe first implementation was just registering a simple gamepad with the same layout as the GBA. The last version allows users to define a `controls.cfg` file with key combos, so games with more complex button requirements are also supported.\n\n**Related code:**\n\n- [VirtualGamepad (first version)](https://github.com/afska/gba-remote-play/blob/v0.9/raspi/src/VirtualGamepad.h#L27)\n- [VirtualGamepad (last version)](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/VirtualGamepad.h#L74)\n- [Controls config](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/out/controls.cfg#L1)\n- [Gamepad name config](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/out/config.cfg#L7)\n\n## Protocol overview\n\nAt the beginning, there's a [Reset packet sync](#reset-packet-sync).\n\nThen, for every frame, the steps to run are:\n\n- _(Reset if needed)_\n- Build frame _(RPI only)_\n- Sync frame start\n- [Metadata exchange](#metadata-exchange)\n- _(If the frame has audio, sync and transfer audio)_\n- Sync pixels start\n- Transfer pixels\n- Sync frame end\n- Render _(GBA only)_\n\n**Related code:**\n\n- [GBA Main loop](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L92)\n- [RPI Main loop](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/GBARemotePlay.h#L48)\n- [Protocol](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/Protocol.h#L4)\n\n### Reset packet sync\n\nPart of the runtime configuration is sent in this _reset_ packet:\n\n```\n10011001100010000111000000000000\n____________________^###****$$$$\n|                   ||  |   |\n \u003e magic number     ||  |    \u003e render mode: frame width and height\n                    ||   \u003e control layout: defined in the configuration file\n\t\t    | \u003e compression: affects temporal diffs' threshold\n\t\t     \u003e CPU overclock flag: if 1, it uses overclocked SPI timings\n```\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eRuntime configuration menu\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/141930047-19e665a9-c24f-40f6-b61a-8c28c5fd7321.png\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [Runtime configuration menu](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/RuntimeConfig.h#L68)\n- [Reset packet sync on the GBA](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L114)\n- [Reset packet sync on the RPI](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/GBARemotePlay.h#L180)\n\n### Metadata exchange\n\nIn this step, the GBA sends its input and receives a _frame metadata_ packet:\n\n```\n00000000000000000000000000000000\n^#**************$$$$$$$$$$$$$$$$\n|||             |\n|||              \u003e start pixel index (for faster GBA rendering)\n|| \u003e number of expected pixel packets\n| \u003e compressed flag: if 1, the frame is RLEncoded\n \u003e audio flag: if 1, the frame includes an audio chunk\n```\n\nAs a sanity check, this transfer is done twice. The second time, each device sends the received packet during the first transfer. If it doesn't match =\u003e **Reset!**\n\n**Related code:**\n\n- [Metadata exchange on the GBA](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L124)\n- [Metadata exchange on the RPI](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/GBARemotePlay.h#L198)\n\n## Audio\n\nFor the audio, the GBA runs [a port](https://github.com/pinobatch/gsmplayer-gba) of the [GSM Full Rate](https://en.wikipedia.org/wiki/Full_Rate) audio codec. It expects 33-byte audio frames, but in order to survive frame drops, GSM frames are grouped into chunks, with their length defined by a build time constant called `AUDIO_CHUNK_SIZE`.\n\n**Related code:**\n\n- [Audio chunk size constant](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/Protocol.h#L16)\n\n### Reading system audio\n\nOn the Raspberry Pi side, we use [a virtual sound card](https://www.alsa-project.org/wiki/Matrix:Module-aloop) that is preinstalled on the system. When you start the module (`sudo modprobe snd-aloop`), two new sound devices appear (both for playing and recording).\n\n\u003ctable border=\"0\"\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n      \u003cp align=\"center\"\u003e\n        \u003ci\u003ePlayback audio devices\u003c/i\u003e\n        \u003cbr\u003e\n        \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125156531-419c9400-e13c-11eb-91df-91ca3dafc386.png\"\u003e\n      \u003c/p\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      \u003cp align=\"center\"\u003e\n        \u003ci\u003eCapture audio devices\u003c/i\u003e\n        \u003cbr\u003e\n        \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125156637-e15a2200-e13c-11eb-8ce3-8e42b57c28d5.png\"\u003e\n      \u003c/p\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nHow it works is that if some application plays sound on -for example- `hw:0,0,0` (card 0, device 0, substream 0), another application can record on `hw:0,1,0` and obtain that sound. The loopback cards have to be set as the default output on the system, so we can record whatever sound is running on the OS.\n\n### Encoding GSM frames\n\nGSM encoding is done with [ffmpeg](http://ffmpeg.org/). The GBA port requires a non-standard rate of 18157Hz, so we have to tell it to ignore its checks, like \"yeah, this is not officially supported, I don't care\", as well as the new rate.\n\nThis is what the recording command looks like:\n\n```\nffmpeg -f alsa -i hw:0,1 -y -ac 1 -af 'aresample=18157' -strict unofficial -c:a gsm -f gsm -loglevel quiet -\n```\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eI swear this is audio!\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125157099-52023e00-e13f-11eb-9316-42b76ef35a27.png\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [LoopbackAudio](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/LoopbackAudio.h#L21)\n\n### Controlling Linux pipes\n\nThe `-` at the end of the _ffmpeg_ command means \"send the result to `stdout`\". The code launches this process with [popen](https://man7.org/linux/man-pages/man3/popen.3.html) and reads through the created pipe.\n\nSince transferring a frame takes time, it can sometimes happen that more audio frames are generated than what we can actually use. If we don't do anything about it, when reading the pipe we'd be actually reading audio _from the past_, producing a snowball of audio lag!\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eOur GBA vibing to outdated audio frames\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125158350-03f13880-e147-11eb-914d-cced48d84969.gif\"\u003e\n\u003c/p\u003e\n\nTo fix that, there's an [ioctl](https://man7.org/linux/man-pages/man2/ioctl.2.html) we can use (called [FIONREAD](https://docs.oracle.com/cd/E19683-01/806-6546/kermes8-28/index.html)) to retrieve the amount of queued bytes. To skip over those, we call the [splice](\u003chttps://en.wikipedia.org/wiki/Splice_(system_call)\u003e) system call to redirect them to `/dev/null`.\n\n**Related code:**\n\n- [Skipping outdated bytes](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/LoopbackAudio.h#L82)\n\n### Decompressing on time\n\nThis was the most complex part of the project. Drawing pixels on the bitmap modes is already a lot of work for the GBA, and now it has to decompress GSM frames! Also, it can't lag. Lots of people tolerate low frame rates on video, but I don't think of anyone who can find acceptable hearing high pitch noises or even silence between audio samples.\n\nWhat I understand _GSMPlayer_ does, is decoding GSM frames, putting the resulting audio samples in a double buffer, and setting up [DMA1](https://www.coranac.com/tonc/text/dma.htm) to copy them to a GBA's audio address, by using a special timing mode that syncs the copy with [Timer 0](https://www.coranac.com/tonc/text/timers.htm).\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eMe attempting to modify GSMPlayer code\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125159152-0d30d400-e14c-11eb-95ca-a5b1cccf8386.png\"\u003e\n\u003c/p\u003e\n\nAudio must be copied on time to prevent stuttering, noises, etc. Regular games do this by using [VBlank](https://www.coranac.com/tonc/text/video.htm#sec-blanks) [interrupts](https://www.coranac.com/tonc/text/interrupts.htm), but that doesn't work here. When transferring at 2.6Mbps there are very few cycles available to process data, and adding an interrupt handler just messes up the packets.\n\nI had to make it so every transfer is cancellable: if it's time to run the audio (we're on the VBlank part), we stop everything, run the audio, and then start a recovery process where we say to the Raspberry Pi where we're at. On start, end, and every `TRANSFER_SYNC_PERIOD` packets of every stream, the Raspi sends a bidirectional packet (at the slow rate) to check if it needs to start the \"recovery mode\".\n\n**Related code:**\n\n- [ReliableStream](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/ReliableStream.h#L9)\n- [Starting recovery process](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/_main.cpp#L274)\n- [Handling recovery process](https://github.com/afska/gba-remote-play/blob/v1.1/raspi/src/ReliableStream.h#L87)\n- [Transfers can be interrupted](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/SPISlave.h#L38)\n\n## EWRAM Overclock\n\nThe GBA code can optionally overclock the external RAM, to use only one [wait state](https://en.wikipedia.org/wiki/Wait_state) instead of two. This process crashes on a GB Micro, but who would use this ~~on a Micro~~ anyway?\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eA guy using a GB Micro with a Raspberry Pi attached to it\u003c/i\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/1631752/125161540-4d4a8380-e159-11eb-8d0a-804bfaf14a49.png\"\u003e\n\u003c/p\u003e\n\n**Related code:**\n\n- [Overclocking EWRAM](https://github.com/afska/gba-remote-play/blob/v1.1/gba/src/Utils.h#L21)\n\n# Setup\n\n## Full guide\n\nhttps://user-images.githubusercontent.com/1631752/142586185-0c524067-aae3-4185-b8b2-9ad5b52a955e.mp4\n\n\u003e [Watch on YouTube](https://www.youtube.com/watch?v=f9Au8nKxzis)\n\n## Quick guide\n\n- Solder a Link Cable to the Raspberry Pi according to the [Normal Mode / SPI](#normal-mode--spi) section of this document.\n- Install [RetroPie](https://retropie.org.uk/docs/First-Installation/).\n- Go to RetroArch options and set:\n  - Settings -\u003e Video -\u003e Scaling -\u003e Aspect ratio -\u003e 4:3\n  - Configuration File -\u003e Save current configuration\n- Run `sudo raspi-config` and set:\n  - **Interface Options:** Enable SPI\n- Run `sudo apt-get install -y wiringpi python-pigpio python3-pigpio`\n- Add the following attributes to `/boot/config.txt`:\n\n```\n# Set Aspect Ratio (4:3)\nhdmi_safe=0\ndisable_overscan=1\nhdmi_group=2\nhdmi_mode=6\n\n# Set GBA Resolution\nframebuffer_width=240\nframebuffer_height=160\n```\n\n- Download the required files (from the [Releases](https://github.com/afska/gba-remote-play/releases) section of this GitHub repo) to `/home/pi/gba-remote-play`.\n- From that directory, run: `chmod +x gbarplay.sh multiboot.tool raspi.run`.\n- Edit `/etc/rc.local` and add `/home/pi/gba-remote-play/gbarplay.sh \u0026` before the `exit 0` line.\n- Reboot and turn on your GBA.\n\n#### Audio (optional)\n\n\u003e It's optional because the Raspberry Pi already has pins for good old analog audio, and you could attach a speaker to it and have clean high-quality sound. On the other hand, audio support here is experimental and heavily decreases the frame rate. Only v1.0 was tested with audio.\n\nIf you want audio coming out from the GBA speakers anyway, here's how:\n\nWhen downloading, use the file `video-and-audio.zip` from the [v1.0 release](https://github.com/afska/gba-remote-play/releases/v1.0).\n\nModify `/etc/modprobe.d/alsa-base.conf` and make it look like this:\n\n```\noptions snd_aloop index=0\noptions snd_bcm2835 index=1\noptions snd_bcm2835 index=2\noptions snd slots=snd-aloop,snd-bcm2835\n```\n\nThen, when you run `cat /proc/asound/modules` you should see:\n\n```\n 0 snd_aloop\n 1 snd_bcm2835\n 2 snd_bcm2835\n```\n\nNow run `sudo modprobe snd-aloop` and set **Loopback (Stereo Full Duplex)** as the default output audio device from the UI.\n\n# GBA Jam 2021\n\nMost of this code was made during the [GBA Jam 2021](https://itch.io/jam/gbajam21). Since this project doesn't fit well into the jam (as it requires external hardware), there's a Demo available in the [Releases](https://github.com/afska/gba-remote-play/releases/v1.0) section where one GBA sends a video with audio to another GBA via Link Cable.\n\nHere's a video of it:\n\nhttps://user-images.githubusercontent.com/1631752/125164337-129c1780-e168-11eb-9bc8-9e2719a7180f.mp4\n\nThe code of that demo is in the [#gba-jam](https://github.com/afska/gba-remote-play/compare/v1.0...gba-jam?expand=1) branch.\n\n# Credits\n\nThis project relies on the following open-source libraries:\n\n- GBA hardware access by [libtonc](https://www.coranac.com/projects/#tonc)\n- GBA audio decompression by [gsmplayer-gba](https://github.com/pinobatch/gsmplayer-gba)\n- GBA multiboot writer by [gba_03_multiboot](https://github.com/akkera102/gba_03_multiboot)\n- Raspberry PI SPI transfers by the [C library for Broadcom BCM 2835](https://www.airspayce.com/mikem/bcm2835/)\n\nThe GBA Jam demo uses these two open Blender clips with Creative Commons licenses:\n\n- [Caminandes 2: Gran Dillama](https://www.youtube.com/watch?v=Z4C82eyhwgU)\n- [Caminandes 3: Llamigos](https://www.youtube.com/watch?v=SkVqJ1SGeL0)\n\nAlso, here are some documentation links that I made use of:\n\n- [TONC](https://www.coranac.com/tonc/text/toc.htm): The greatest GBA tutorial ever made.\n- [GBATEK](https://problemkaputt.de/gbatek.htm): Documentation of NO$GBA with GBA internals.\n- [rpi-fbcp's dispmanx API usage](https://github.com/tasanakorn/rpi-fbcp/blob/master/main.c)\n- [Linux uinput's usage tutorial](https://blog.marekkraus.sk/c/linuxs-uinput-usage-tutorial-virtual-gamepad/)\n- [Playing with ALSA loopback devices](https://sysplay.in/blog/linux/2019/06/playing-with-alsa-loopback-devices/)\n\nSpecial thanks to my friend Lucas Fryzek ([@Hazematman](https://github.com/Hazematman)), who has a deep knowledge of embedded systems and helped me a lot with design decisions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafska%2Fgba-remote-play","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fafska%2Fgba-remote-play","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fafska%2Fgba-remote-play/lists"}