Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

Commit

Permalink
Allow to read audio/video from io.Reader
Browse files Browse the repository at this point in the history
  • Loading branch information
nmorenor committed Apr 6, 2023
1 parent ab2f7da commit d036328
Show file tree
Hide file tree
Showing 6 changed files with 658 additions and 10 deletions.
17 changes: 17 additions & 0 deletions examples/player-stream/bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import os, fileinput, shutil

bundleDirPath = os.path.abspath("bundle")
os.makedirs(bundleDirPath, exist_ok=True)

for dllInfo in fileinput.input():
dllInfo = dllInfo.strip()
dllInfoParts = dllInfo.split(sep=" ")
dllName = dllInfoParts[0]
dllPath = dllInfoParts[2]
dllBundlePath = os.path.join(bundleDirPath, dllName)

if dllPath.startswith("/mingw64/bin"):
dllPath = os.path.join("C:/msys64", dllPath[1:])
shutil.copyfile(dllPath, dllBundlePath)

shutil.copyfile("player.exe", "bundle/player.exe")
Binary file added examples/player-stream/demo.mkv
Binary file not shown.
Binary file added examples/player-stream/demo.mp4
Binary file not shown.
358 changes: 358 additions & 0 deletions examples/player-stream/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
package main

import (
"bytes"
"encoding/binary"
"fmt"
"image"
"io"
"os"
"time"

"github.com/faiface/beep"
"github.com/faiface/beep/speaker"
"github.com/hajimehoshi/ebiten"
_ "github.com/silbinarywolf/preferdiscretegpu"
"github.com/zergon321/reisen"
)

const (
width = 1280
height = 720
frameBufferSize = 1024
sampleRate = 44100
channelCount = 2
bitDepth = 8
sampleBufferSize = 32 * channelCount * bitDepth * 1024
SpeakerSampleRate beep.SampleRate = 44100
)

// readVideoAndAudio reads video and audio frames
// from the opened media and sends the decoded
// data to che channels to be played.
func readVideoAndAudio(media *reisen.Media) (<-chan *image.RGBA, <-chan [2]float64, chan error, error) {
frameBuffer := make(chan *image.RGBA,
frameBufferSize)
sampleBuffer := make(chan [2]float64, sampleBufferSize)
errs := make(chan error)

err := media.OpenDecode()

if err != nil {
return nil, nil, nil, err
}

videoStream := media.VideoStreams()[0]
err = videoStream.Open()

if err != nil {
return nil, nil, nil, err
}

audioStream := media.AudioStreams()[0]
err = audioStream.Open()

if err != nil {
return nil, nil, nil, err
}

/*err = media.Streams()[0].Rewind(60 * time.Second)
if err != nil {
return nil, nil, nil, err
}*/

/*err = media.Streams()[0].ApplyFilter("h264_mp4toannexb")
if err != nil {
return nil, nil, nil, err
}*/

go func() {
for {
packet, gotPacket, err := media.ReadPacket()

if err != nil {
continue
}

if !gotPacket {
break
}

/*hash := sha256.Sum256(packet.Data())
fmt.Println(base58.Encode(hash[:]))*/

switch packet.Type() {
case reisen.StreamVideo:
s := media.Streams()[packet.StreamIndex()].(*reisen.VideoStream)
videoFrame, gotFrame, err := s.ReadVideoFrame()

if err != nil {
continue
}

if !gotFrame {
break
}

if videoFrame == nil {
continue
}

frameBuffer <- videoFrame.Image()

case reisen.StreamAudio:
s := media.Streams()[packet.StreamIndex()].(*reisen.AudioStream)
audioFrame, gotFrame, err := s.ReadAudioFrame()

if err != nil {
go func(err error) {
errs <- err
}(err)
}

if !gotFrame {
break
}

if audioFrame == nil {
continue
}

// Turn the raw byte data into
// audio samples of type [2]float64.
reader := bytes.NewReader(audioFrame.Data())

// See the README.md file for
// detailed scheme of the sample structure.
for reader.Len() > 0 {
sample := [2]float64{0, 0}
var result float64
err = binary.Read(reader, binary.LittleEndian, &result)

if err != nil {
go func(err error) {
errs <- err
}(err)
}

sample[0] = result

err = binary.Read(reader, binary.LittleEndian, &result)

if err != nil {
go func(err error) {
errs <- err
}(err)
}

sample[1] = result
sampleBuffer <- sample
}
}
}

videoStream.Close()
audioStream.Close()
media.CloseDecode()
close(frameBuffer)
close(sampleBuffer)
close(errs)
}()

return frameBuffer, sampleBuffer, errs, nil
}

// streamSamples creates a new custom streamer for
// playing audio samples provided by the source channel.
//
// See https://github.com/faiface/beep/wiki/Making-own-streamers
// for reference.
func streamSamples(sampleSource <-chan [2]float64) beep.Streamer {
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
numRead := 0

for i := 0; i < len(samples); i++ {
sample, ok := <-sampleSource

if !ok {
numRead = i + 1
break
}

samples[i] = sample
numRead++
}

if numRead < len(samples) {
return numRead, false
}

return numRead, true
})
}

// Game holds all the data
// necessary for playing video.
type Game struct {
videoSprite *ebiten.Image
ticker <-chan time.Time
errs <-chan error
frameBuffer <-chan *image.RGBA
fps int
videoTotalFramesPlayed int
videoPlaybackFPS int
perSecond <-chan time.Time
last time.Time
deltaTime float64
}

// Strarts reading samples and frames
// of the media file.
func (game *Game) Start(fname string) error {
// Initialize the audio speaker.
err := speaker.Init(sampleRate,
SpeakerSampleRate.N(time.Second/10))

if err != nil {
return err
}

// Sprite for drawing video frames.
game.videoSprite, err = ebiten.NewImage(
width, height, ebiten.FilterDefault)

if err != nil {
return err
}

file, err := os.Open("demo.mkv")
if err != nil {
return err
}
readseek := io.ReadSeeker(file)
// Open the media file.
media, err := reisen.NewMediaFromReadSeeker("demo", 4096, readseek)
// media, err := reisen.NewMedia("demo.mp4")

if err != nil {
return err
}

// Get the FPS for playing
// video frames.
videoFPS, _ := media.Streams()[0].FrameRate()

if err != nil {
return err
}

// SPF for frame ticker.
spf := 1.0 / float64(videoFPS)
frameDuration, err := time.
ParseDuration(fmt.Sprintf("%fs", spf))

if err != nil {
return err
}

// Start decoding streams.
var sampleSource <-chan [2]float64
game.frameBuffer, sampleSource,
game.errs, err = readVideoAndAudio(media)

if err != nil {
return err
}

// Start playing audio samples.
speaker.Play(streamSamples(sampleSource))

game.ticker = time.Tick(frameDuration)

// Setup metrics.
game.last = time.Now()
game.fps = 0
game.perSecond = time.Tick(time.Second)
game.videoTotalFramesPlayed = 0
game.videoPlaybackFPS = 0

return nil
}

func (game *Game) Update(screen *ebiten.Image) error {
// Compute dt.
game.deltaTime = time.Since(game.last).Seconds()
game.last = time.Now()

// Check for incoming errors.
select {
case err, ok := <-game.errs:
if ok {
return err
}

default:
}

// Read video frames and draw them.
select {
case <-game.ticker:
frame, ok := <-game.frameBuffer

if ok {
game.videoSprite.ReplacePixels(frame.Pix)

game.videoTotalFramesPlayed++
game.videoPlaybackFPS++
}

default:
}

// Draw the video sprite.
op := &ebiten.DrawImageOptions{}
err := screen.DrawImage(game.videoSprite, op)

if err != nil {
return err
}

game.fps++

// Update metrics in the window title.
select {
case <-game.perSecond:
ebiten.SetWindowTitle(fmt.Sprintf("%s | FPS: %d | dt: %f | Frames: %d | Video FPS: %d",
"Video", game.fps, game.deltaTime, game.videoTotalFramesPlayed, game.videoPlaybackFPS))

game.fps = 0
game.videoPlaybackFPS = 0

default:
}

return nil
}

func (game *Game) Layout(a, b int) (int, int) {
return width, height
}

func main() {
game := &Game{}
err := game.Start("demo.mp4")
handleError(err)

ebiten.SetWindowSize(width, height)
ebiten.SetWindowTitle("Video")
err = ebiten.RunGame(game)
handleError(err)
}

func handleError(err error) {
if err != nil {
panic(err)
}
}
Loading

0 comments on commit d036328

Please sign in to comment.