Skip to content

vgonkivs/love

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LOVE

Live Onchain Video Environment


What is LOVE?

LOVE is a decentralized live streaming platform built on Celestia's data availability layer. It captures video and audio from your webcam/microphone, encodes them using H.264 video compression, multiplexes them into 2MB data chunks (~8 seconds of A/V), and submits them as blobs to the Celestia blockchain. Viewers can then fetch these blobs and play back the stream in real-time with synchronized audio and video.

Features

  • Live Streaming: Real-time video capture from webcam with configurable resolution and framerate
  • H.264 Video Compression: Efficient video encoding using ffmpeg for optimal streaming
  • Audio Support: Synchronized audio capture from microphone (16-bit PCM, configurable sample rate)
  • Local Preview: Optional local preview window for monitoring your stream
  • On-chain Storage: Stream data is stored as Celestia blobs with automatic gas estimation
  • A/V Sync: Timestamp-based synchronization ensures proper audio/video playback
  • Background Prefetching: Viewer fetches blobs in background for smooth playback
  • Decentralized: No central server - streams go directly to the blockchain
  • Censorship Resistant: Once on-chain, streams cannot be removed
  • Pluggable Codec: Interface-based design allows swapping encoding implementations

Quick Start

Prerequisites

  1. Go 1.21+

  2. ffmpeg (required for H.264 encoding/decoding)

    # macOS
    brew install ffmpeg
    
    # Ubuntu/Debian
    apt install ffmpeg
  3. OpenCV 4.x with GoCV bindings

    # macOS
    brew install opencv
    
    # Ubuntu/Debian
    apt install libopencv-dev
  4. Audio libraries (Linux only)

    # Ubuntu/Debian (ALSA)
    apt install libasound2-dev
  5. Celestia light node running locally (or remote node access)

  6. Auth token for Celestia node

Installation

git clone https://github.com/vgonkivs/love.git
cd love
make build

Get Celestia Auth Token

celestia light auth admin --p2p.network <network>

Usage

Using Make (Recommended)

# Stream with local preview
make stream token=<auth_token>

# Stream with custom settings
make stream token=<auth_token> fps=15 width=640 height=480

# View a stream
make view token=<auth_token> namespace=<hex> start_height=<height>

# Show help
make help

Using Binary Directly

# Basic streaming
./love stream -token <auth_token>

# Custom settings
./love stream -width 1920 -height 1080 -fps 30 -bitrate 4M -samplerate 48000 -token <auth_token>

# View a stream
./love view -namespace <namespace_hex> -height <start_height> -token <auth_token>

Press ESC to stop streaming or exit viewer.

Make Variables

Variable Default Description
token Celestia auth token (required)
node http://localhost:26658 Celestia node URL
camera 0 Camera device ID
width 1280 Video width (pixels)
height 720 Video height (pixels)
fps 30 Frames per second
namespace Stream namespace hex (required for view)
start_height Start block height (required for view)

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              STREAMING                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │                         Capturer                                      │  │
│   │  1. Send entrypoint blob (metadata)                                  │  │
│   │  2. Initialize devices:                                              │  │
│   │  ┌──────────┐                                                        │  │
│   │  │  Webcam  │──┐                                                     │  │
│   │  └──────────┘  │    ┌──────────────┐    ┌─────────────┐              │  │
│   │                ├───▶│   Encoder    │───▶│ Preview     │              │  │
│   │  ┌──────────┐  │    │  (H.264)     │    │ Window      │              │  │
│   │  │   Mic    │──┘    └──────────────┘    └─────────────┘              │  │
│   │  └──────────┘              │                                         │  │
│   │                            ▼                                         │  │
│   │                      2MB Blobs                                       │  │
│   │                      3. Send stream end blob                         │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                          │                                   │
│                                          ▼                                   │
│                              ┌──────────────────────┐                        │
│                              │      Streamer        │                        │
│                              │  - Random namespace  │                        │
│                              │  - Submit to Celestia│                        │
│                              └──────────┬───────────┘                        │
│                                         │                                    │
└─────────────────────────────────────────┼────────────────────────────────────┘
                                          │
                                          ▼
                               ┌──────────────────────┐
                               │   Celestia Network   │
                               │                      │
                               │   Blobs stored in    │
                               │   namespace at       │
                               │   sequential heights │
                               └──────────┬───────────┘
                                          │
┌─────────────────────────────────────────┼────────────────────────────────────┐
│                              VIEWING    │                                    │
├─────────────────────────────────────────┼────────────────────────────────────┤
│                                         ▼                                    │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │                          Viewer                                       │  │
│   │                                                                       │  │
│   │  ┌─────────────────┐    ┌──────────────┐    ┌────────────────────┐   │  │
│   │  │ Background      │───▶│   Decoder    │───▶│  Display (GoCV)    │   │  │
│   │  │ Blob Fetcher    │    │  (H.264)     │    └────────────────────┘   │  │
│   │  │ (prefetching)   │    │              │    ┌────────────────────┐   │  │
│   │  └─────────────────┘    │              │───▶│  Audio Player      │   │  │
│   │                         └──────────────┘    │  (malgo)           │   │  │
│   │                                             └────────────────────┘   │  │
│   │  A/V Sync: Video paced by timestamps, audio plays at sample rate    │  │
│   └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

Codec Interface

LOVE uses a pluggable codec architecture. The current implementation is H264Encoder/Decoder using ffmpeg, but the interface allows for alternative implementations:

// Encoder encodes video and audio frames for streaming
type Encoder interface {
    EncodeVideo(frame gocv.Mat, timestamp time.Duration, sequence uint32) ([]byte, error)
    EncodeAudio(samples []byte, timestamp time.Duration, sequence uint32) ([]byte, error)
    CreateEntrypoint(sampleRate int, channels int, fps int) []byte
    CreateStreamEnd(totalDuration time.Duration, totalFrames uint32) []byte
}

// Decoder decodes multiplexed video and audio frames
type Decoder interface {
    Decode(data []byte) (*DecodedFrame, int)
    ParseEntrypoint(data []byte) (sampleRate int, channels int, fps int, valid bool)
}

Data Format

Frame Header (20 bytes)

Each video/audio frame is prefixed with a header:

┌───────────┬───────────┬─────────────────┬──────────────┐
│  Marker   │   Size    │   Timestamp     │   Sequence   │
│  4 bytes  │  4 bytes  │    8 bytes      │   4 bytes    │
├───────────┼───────────┼─────────────────┼──────────────┤
│ "H264" or │  Payload  │  Nanoseconds    │   Frame      │
│ "AUDF"    │  length   │  since start    │   number     │
└───────────┴───────────┴─────────────────┴──────────────┘
  • H264: H.264 encoded video frame (may contain multiple NAL units: SPS, PPS, IDR, P-frames)
  • AUDF: Audio frame (16-bit PCM samples)

Blob Structure

Frames are accumulated into 2MB blobs (~8 seconds of A/V at 2Mbps video + 128kbps audio):

┌─────────────────────────────────────────────────────────┐
│                    2MB Blob                             │
├─────────────────────────────────────────────────────────┤
│ [Header][H.264 Data][Header][PCM Data][Header][H.264]...│
└─────────────────────────────────────────────────────────┘

Entrypoint Blob

The Capturer sends an entrypoint blob first (before camera initialization) with stream metadata:

┌───────────┬─────────────┬──────────┬─────────┬───────┬───────┬────────┐
│  Marker   │ Sample Rate │ Channels │   FPS   │ Codec │ Width │ Height │
│  4 bytes  │   4 bytes   │  1 byte  │ 1 byte  │1 byte │2 bytes│2 bytes │
├───────────┼─────────────┼──────────┼─────────┼───────┼───────┼────────┤
│  "ENTR"   │   44100     │    1     │   30    │   1   │ 1280  │  720   │
└───────────┴─────────────┴──────────┴─────────┴───────┴───────┴────────┘

Codec: 0 = JPEG (legacy), 1 = H.264

Stream End Blob

When the stream ends gracefully (ESC or Ctrl+C), the Capturer sends a stream end notification:

┌───────────┬─────────────────────┬──────────────┐
│  Marker   │  Total Duration     │ Total Frames │
│  4 bytes  │     8 bytes         │   4 bytes    │
├───────────┼─────────────────────┼──────────────┤
│  "ENDS"   │  Nanoseconds        │   Count      │
└───────────┴─────────────────────┴──────────────┘

This allows viewers to distinguish between "stream ended gracefully" vs "stream stopped unexpectedly".

Command Reference

Stream Options

Option Default Description
-camera 0 Camera device ID
-width 1280 Video width (pixels)
-height 720 Video height (pixels)
-fps 30 Frames per second
-bitrate 2M H.264 bitrate (e.g., 2M, 4M)
-samplerate 44100 Audio sample rate (Hz)
-node http://localhost:26658 Celestia node URL
-token Auth token (required)

View Options

Option Default Description
-namespace Stream namespace hex (required)
-height Start block height (required)
-node http://localhost:26658 Celestia node URL
-token Auth token (required)

How It Works

Streaming

  1. Entrypoint: Capturer sends entrypoint blob with stream metadata (sample rate, channels, fps, dimensions, codec)
  2. Initialize: Capturer opens webcam (GoCV) and microphone (malgo)
  3. Preview: Frames are displayed in local preview window (optional)
  4. Encode: Video frames are H.264 encoded via ffmpeg (SPS/PPS/IDR combined), audio is 16-bit PCM
  5. Multiplex: Frames are tagged with H264/AUDF markers and timestamps
  6. Chunk: Data is accumulated into 2MB buffers inside Capturer (~8 seconds of A/V)
  7. Submit: Blobs are submitted to Celestia via Streamer with automatic gas estimation
  8. Stream End: When stopping gracefully, Capturer sends stream end blob with total duration and frame count

Viewing

  1. Connect: Viewer connects to Celestia node
  2. Find Entrypoint: Locate the ENTR blob with stream parameters and codec type
  3. Create Decoder: Initialize H.264 decoder based on codec identifier
  4. Background Fetch: Goroutine prefetches blobs at sequential block heights into a buffered channel
  5. Decode: Parse frame headers, decode H.264 video via ffmpeg, extract PCM audio
  6. A/V Sync: Video is paced by timestamps, audio plays at native sample rate through malgo
  7. Display: Show video in window, play audio through speakers
  8. SPS/PPS Caching: Decoder caches parameter sets for mid-stream joining

Package Structure

love/
├── main.go              # CLI entry point
├── Makefile             # Build and run commands
├── cmd/
│   └── chaintest/       # H.264 encode/decode chain test app
│       └── main.go
├── lib/
│   ├── capture/         # Video + audio capture with embedded encoder
│   │   ├── capture.go   # Capturer implementation
│   │   └── config.go    # Capture configuration
│   ├── codec/           # Encoding/decoding interfaces and implementations
│   │   ├── interface.go # Encoder/Decoder interfaces
│   │   ├── jpeg.go      # JPEGCodec implementation (legacy)
│   │   ├── h264_encoder.go # H.264 encoder using ffmpeg
│   │   ├── h264_decoder.go # H.264 decoder using ffmpeg
│   │   ├── codec.go     # Shared constants and helpers
│   │   └── decoder.go   # Frame decoding utilities
│   ├── streamer/        # Celestia blob submission
│   │   ├── streamer.go  # Streamer implementation
│   │   └── config.go    # Streamer configuration
│   └── viewer/          # Blob fetching + playback with embedded decoder
│       ├── viewer.go    # Viewer implementation (background fetcher + A/V sync)
│       └── config.go    # Viewer configuration

Troubleshooting

Video not displaying

  • Ensure ffmpeg is installed: ffmpeg -version
  • Check if OpenCV/GoCV is properly installed

Audio not working

  • Linux: Install ALSA dev libraries: apt install libasound2-dev
  • Check microphone permissions

"non-existing PPS referenced" errors

  • This happens when joining mid-stream before a keyframe
  • Wait for the next keyframe or restart from an earlier height

A/V out of sync

  • Ensure you're using a fresh recording (old recordings may have sync issues)
  • The viewer uses timestamp-based sync - video paced by timestamps, audio at native rate

License

MIT


Go live with LOVE

About

Go live with LOVE

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors