Using a Raspberry Pi for TV streaming in 2026 means moving past the 1080p limitations of older boards and leveraging true hardware-accelerated 4K60 HEVC decoding. The Raspberry Pi 5 handles high-bitrate local streams and HDR10 metadata without dropping frames. But the real hallmark of a professional media center build isn't just video output; it's control integration. By combining HDMI-CEC (Consumer Electronics Control) with a custom I2C OLED status display and a hardware safe-shutdown button, you eliminate the need for a dedicated remote and protect your filesystem from corruption.

Difficulty Rating: Intermediate | Time: 2 Hours | Cost: ~$115 USD

Hardware Spec Sheet & Component Selection

Before wiring anything, we need to address the silicon. The Pi 4 is still floating around maker bins, but its VideoCore VI GPU struggles with 4K60 HEVC (H.265) streams, relying on software decoding that causes thermal throttling and stutter. The Pi 5's VideoCore VII includes a dedicated HEVC hardware pipeline. Here is the data-dense breakdown of why the Pi 5 is the mandatory choice for a modern streaming build.

Feature Raspberry Pi 4 Model B (4GB) Raspberry Pi 5 (8GB)
SoC / GPU BCM2711 / VideoCore VI BCM2712 / VideoCore VII
Max Resolution 4Kp60 (H.264 only) 4Kp60 (H.265/HEVC hardware decode)
USB / PCIe USB 3.0 (shared bus bottleneck) USB 3.0 (dedicated) + PCIe 2.0 x1
Power Delivery 5V/3A (15W) 5V/5A USB-C PD (27W required for full peripheral current)
2026 Street Price ~$55 USD ~$80 USD

Exact Parts List

  • Compute: Raspberry Pi 5 (8GB variant) - $80
  • Thermal: Official Pi 5 Active Cooler - $5 (Do not use passive cases for enclosed TV stands; ambient temps will throttle the BCM2712).
  • Power: Official 27W USB-C PD Power Supply - $12 (Crucial: Third-party 5V/3A phone chargers will trigger peripheral brownouts when the HDMI and I2C buses draw peak current).
  • Storage: 128GB SanDisk Extreme microSD (A2 Application Class) - $15. The A2 rating handles Kodi's SQLite database I/O operations without UI lag.
  • Display: 0.96-inch SSD1306 I2C OLED Module (128x64) - $4
  • Control: 6x6mm Tactile Pushbutton Switch + 2x female-to-female jumper wires - $1

Pin Mapping & Physical Assembly

We are integrating two hardware peripherals onto the 40-pin header: an I2C OLED for network/status telemetry, and a physical safe-shutdown button. The Pi 5's BCM2712 chip maintains backward-compatible pinouts for standard GPIO and I2C functions, but requires precise mapping to avoid shorting the 3.3V logic rail.

Callout Tip: The I2C bus on the Pi 5 has a capacitance limit of roughly 400pF. The SSD1306 OLED draws minimal capacitance, but keep your SDA/SCL jumper wires under 12 inches to prevent signal degradation and ghosting on the display.
Component Module Pin Pi 5 Physical Pin BCM GPIO Function Notes
OLED VCC 1 3.3V Power Never connect to 5V (Pin 2) or you will fry the SSD1306 controller.
OLED GND 6 Ground Common ground reference.
OLED SCL 5 GPIO 3 I2C Clock (includes 1.8kΩ on-board pull-up).
OLED SDA 3 GPIO 2 I2C Data.
Button Leg 1 29 GPIO 5 Configured with internal software pull-up.
Button Leg 2 30 Ground Pulls GPIO 5 LOW when pressed.

Software Setup & Python Control Script

Target Board Variant: This code and configuration specifically target the Raspberry Pi 5 (8GB) running Raspberry Pi OS Bookworm (64-bit). While Kodi can be installed via sudo apt install kodi, we are running a custom Python daemon alongside it to manage the OLED telemetry and hardware shutdown.

First, enable the I2C interface and install the required Python libraries:

sudo raspi-config  # Navigate to Interface Options -> I2C -> Enable
sudo apt update
sudo apt install python3-pip python3-gpiozero i2c-tools
pip3 install luma.oled Pillow --break-system-packages

Below is the complete, compilable Python script. It initializes the I2C display, polls the network IP, and sets up an interrupt-driven hardware shutdown button. Save this as media_center_daemon.py.

import time
import sys
import subprocess
from gpiozero import Button
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont

# --- PIN & ADDRESS DEFINITIONS ---
SHUTDOWN_PIN = 5       # BCM GPIO 5 (Physical Pin 29)
I2C_PORT = 1           # Default I2C bus on Pi 5
I2C_ADDRESS = 0x3C     # Standard SSD1306 address

def get_ip_address():
    """Fetches the primary IPv4 address for the OLED display."""
    try:
        cmd = "hostname -I | awk '{print $1}'"
        ip = subprocess.check_output(cmd, shell=True).decode('utf-8').strip()
        return ip if ip else 'No Network'
    except Exception:
        return 'Network Error'

def safe_shutdown(display, font):
    """Handles the hardware button press, updates OLED, and halts the OS."""
    with canvas(display) as draw:
        draw.text((0, 0), 'SYSTEM HALT', font=font, fill='white')
        draw.text((0, 20), 'Writing cache...', font=font, fill='white')
    time.sleep(1.5) # Allow OLED to render before kernel stops I2C
    subprocess.run(['sudo', 'shutdown', '-h', 'now'])

if __name__ == '__main__':
    try:
        # Initialize I2C OLED
        serial = i2c(port=I2C_PORT, address=I2C_ADDRESS)
        display = ssd1306(serial)
        font = ImageFont.load_default()

        # Initialize GPIO Button with internal pull-up and debounce
        btn = Button(SHUTDOWN_PIN, pull_up=True, bounce_time=0.2)
        btn.when_pressed = lambda: safe_shutdown(display, font)

        print(f'Daemon active. Listening for shutdown on GPIO {SHUTDOWN_PIN}...')

        # Main telemetry loop
        while True:
            ip = get_ip_address()
            with canvas(display) as draw:
                draw.text((0, 0), 'Pi5 Media Ctr', font=font, fill='white')
                draw.text((0, 15), f'IP: {ip}', font=font, fill='white')
                draw.text((0, 30), 'CEC: Standby', font=font, fill='white')
                draw.text((0, 45), 'Btn: Armed', font=font, fill='white')
            time.sleep(5) # 5-second refresh to minimize I2C bus spam

    except FileNotFoundError as e:
        print(f'FATAL I2C Error: {e}. Is I2C enabled in raspi-config?')
        sys.exit(1)
    except OSError as e:
        print(f'FATAL I2C Hardware Error: {e}. Check SDA/SCL wiring and pull-ups.')
        sys.exit(1)
    except Exception as e:
        print(f'Unexpected daemon error: {e}')
        sys.exit(1)

Debugging: I2C Failures & CEC Handshake Errors

Embedded media centers live and die by their bus communications. When your build fails, it usually manifests in two specific error domains: the local I2C bus or the external HDMI-CEC bus. Here is the exact decision path for the most common faults.

First Three Things to Check When It Fails

  1. Verify I2C Bus State: Run i2cdetect -y 1 in the terminal. If you don't see 3c in the grid, your wiring is wrong or the interface is disabled.
  2. Verify HDMI Cable Pin 13: CEC relies on Pin 13 of the HDMI connector. Many cheap, uncertified 'high speed' cables omit this wire to save copper. Swap to a certified cable.
  3. Verify TV-Side CEC Enablement: The Pi cannot initiate CEC if the TV's bus is asleep. Ensure Anynet+ (Samsung), Bravia Sync (Sony), or SimpLink (LG) is explicitly toggled ON in the TV's settings menu.

Exact Error Strings & Ranked Causes

Error 1: FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'
Ranked Causes:
1. I2C interface is disabled in the OS kernel overlay. Fix: Run sudo raspi-config and enable I2C, then reboot.
2. You are running a minimal container or Docker image without device passthrough. Fix: Add --device /dev/i2c-1 to your Docker run command.
Error 2: OSError: [Errno 121] Remote I/O error
Ranked Causes:
1. Missing ground reference between the Pi and the OLED. Fix: Verify Physical Pin 6 is securely connected.
2. SDA/SCL lines swapped. Fix: Swap the wires on Physical Pins 3 and 5.
3. I2C address mismatch. Some SSD1306 clones ship at 0x3D. Fix: Change I2C_ADDRESS = 0x3C to 0x3D in the Python script.
Error 3 (Kodi Log): CEC adapter not found or libCEC: unable to open device
Ranked Causes:
1. The libcec package is missing from the OS. Fix: Run sudo apt install libcec6 kodi-inputstream-adaptive.
2. TV CEC bus is overloaded (too many devices trying to act as the 'root' controller). Fix: Unplug other CEC devices (like Roku or FireTV sticks) to clear the bus arbitration.
3. Kernel module conflict. Fix: Add dtoverlay=vc4-kms-v3d to /boot/firmware/config.txt to ensure the KMS driver properly exposes the CEC endpoint to user-space.

Extending and Simplifying the Build

Depending on your deployment environment, you may want to strip this build down to its bare essentials or expand it into a universal remote hub.

How to Simplify (The 'Flash and Forget' Route)

If you don't care about the custom Python OLED telemetry and just want a working streaming box, drop Raspberry Pi OS entirely. Download LibreELEC for the Pi 5. Flash it to your microSD card using the Raspberry Pi Imager. LibreELEC is a 'Just Enough OS' Linux distribution that boots directly into Kodi. It includes pre-compiled libcec drivers out of the box, meaning your TV remote will control Kodi via HDMI-CEC the second it boots, with zero terminal commands required.

How to Extend (Adding Legacy IR Control)

HDMI-CEC is excellent, but it only passes basic directional and select commands from your TV remote. If you want to use a dedicated media remote with custom macros, add an IR receiver.

  • Component: TSOP38238 IR Receiver (38kHz carrier).
  • Wiring: Connect VCC to 3.3V, GND to Ground, and the Data Out pin to BCM GPIO 4 (Physical Pin 7).
  • Software: Install ir-keytable via apt. You can map the raw IR scancodes to Linux input events, allowing you to control Kodi even if your TV's CEC implementation is buggy or disabled.

Building a Raspberry Pi for TV streaming bridges the gap between a hobbyist breadboard project and a permanent living room appliance. By respecting the Pi 5's power delivery requirements, properly terminating your I2C bus, and leveraging the CEC handshake, you get a 4K media center that feels like a commercial product.