added video codecs, works with mjpeg & h264-ffmpeg

This commit is contained in:
amit bezalel 2017-12-30 09:29:33 +02:00
parent 16f897ddfe
commit a855abcb6c
12 changed files with 455 additions and 102 deletions

84
encoders/dv8-enc.go Normal file
View File

@ -0,0 +1,84 @@
package encoders
import (
"image"
"io"
"os"
"os/exec"
"strings"
"vnc2webm/logger"
)
type DV8ImageEncoder struct {
cmd *exec.Cmd
input io.WriteCloser
}
func (enc *DV8ImageEncoder) Init(videoFileName string) {
fileExt := ".webm"
if !strings.HasSuffix(videoFileName, fileExt) {
videoFileName = videoFileName + fileExt
}
binary := "./ffmpeg"
cmd := exec.Command(binary,
"-f", "image2pipe",
"-vcodec", "ppm",
//"-r", strconv.Itoa(framerate),
"-r", "3",
//"-i", "pipe:0",
"-i", "-",
"-vcodec", "libvpx", //"libvpx",//"libvpx-vp9"//"libx264"
"-b:v", "2M",
"-threads", "8",
//"-speed", "0",
//"-lossless", "1", //for vpx
// "-tile-columns", "6",
//"-frame-parallel", "1",
// "-an", "-f", "webm",
"-cpu-used", "-16",
"-preset", "ultrafast",
"-deadline", "realtime",
//"-cpu-used", "-5",
"-maxrate", "2.5M",
"-bufsize", "10M",
"-g", "6",
//"-rc_lookahead", "16",
//"-profile", "0",
"-qmax", "51",
"-qmin", "11",
//"-slices", "4",
//"-vb", "2M",
videoFileName,
)
//cmd := exec.Command("/bin/echo")
//io.Copy(cmd.Stdout, os.Stdout)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
encInput, err := cmd.StdinPipe()
enc.input = encInput
if err != nil {
logger.Error("can't get ffmpeg input pipe")
}
enc.cmd = cmd
}
func (enc *DV8ImageEncoder) Run() {
logger.Debugf("launching binary: %v", enc.cmd.Args)
err := enc.cmd.Run()
if err != nil {
logger.Error("error while launching ffmpeg:", err)
}
}
func (enc *DV8ImageEncoder) Encode(img image.Image) {
err := encodePPM(enc.input, img)
if err != nil {
logger.Error("error while encoding image:", err)
}
}
func (enc *DV8ImageEncoder) Close() {
}

84
encoders/dv9-enc.go Normal file
View File

@ -0,0 +1,84 @@
package encoders
import (
"image"
"io"
"os"
"os/exec"
"strings"
"vnc2webm/logger"
)
type DV9ImageEncoder struct {
cmd *exec.Cmd
input io.WriteCloser
}
func (enc *DV9ImageEncoder) Init(videoFileName string) {
fileExt := ".webm"
if !strings.HasSuffix(videoFileName, fileExt) {
videoFileName = videoFileName + fileExt
}
binary := "./ffmpeg"
cmd := exec.Command(binary,
"-f", "image2pipe",
"-vcodec", "ppm",
//"-r", strconv.Itoa(framerate),
"-r", "3",
//"-i", "pipe:0",
"-i", "-",
"-vcodec", "libvpx-vp9", //"libvpx",//"libvpx-vp9"//"libx264"
"-b:v", "2M",
"-threads", "8",
//"-speed", "0",
//"-lossless", "1", //for vpx
// "-tile-columns", "6",
//"-frame-parallel", "1",
// "-an", "-f", "webm",
"-cpu-used", "-16",
"-preset", "ultrafast",
"-deadline", "realtime",
//"-cpu-used", "-5",
"-maxrate", "2.5M",
"-bufsize", "10M",
"-g", "6",
//"-rc_lookahead", "16",
//"-profile", "0",
"-qmax", "51",
"-qmin", "11",
//"-slices", "4",
//"-vb", "2M",
videoFileName,
)
//cmd := exec.Command("/bin/echo")
//io.Copy(cmd.Stdout, os.Stdout)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
encInput, err := cmd.StdinPipe()
enc.input = encInput
if err != nil {
logger.Error("can't get ffmpeg input pipe")
}
enc.cmd = cmd
}
func (enc *DV9ImageEncoder) Run() {
logger.Debugf("launching binary: %v", enc.cmd.Args)
err := enc.cmd.Run()
if err != nil {
logger.Error("error while launching ffmpeg:", err)
}
}
func (enc *DV9ImageEncoder) Encode(img image.Image) {
err := encodePPM(enc.input, img)
if err != nil {
logger.Error("error while encoding image:", err)
}
}
func (enc *DV9ImageEncoder) Close() {
}

43
encoders/image-enc.go Normal file
View File

@ -0,0 +1,43 @@
package encoders
import (
"fmt"
"image"
"image/color"
"io"
)
func encodePPM(w io.Writer, img image.Image) error {
maxvalue := 255
size := img.Bounds()
// write ppm header
_, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue)
if err != nil {
return err
}
// write the bitmap
colModel := color.RGBAModel
row := make([]uint8, size.Dx()*3)
for y := size.Min.Y; y < size.Max.Y; y++ {
i := 0
for x := size.Min.X; x < size.Max.X; x++ {
color := colModel.Convert(img.At(x, y)).(color.RGBA)
row[i] = color.R
row[i+1] = color.G
row[i+2] = color.B
i += 3
}
if _, err := w.Write(row); err != nil {
return err
}
}
return nil
}
type ImageEncoder interface {
Init(string)
Run()
Encode(image.Image)
Close()
}

56
encoders/mjpeg-enc.go Normal file
View File

@ -0,0 +1,56 @@
package encoders
import (
"bytes"
"image"
"image/jpeg"
"strings"
"vnc2webm/logger"
"github.com/icza/mjpeg"
)
type MJPegImageEncoder struct {
avWriter mjpeg.AviWriter
Quality int
Framerate int32
}
func (enc *MJPegImageEncoder) Init(videoFileName string) {
fileExt := ".avi"
if !strings.HasSuffix(videoFileName, fileExt) {
videoFileName = videoFileName + fileExt
}
if enc.Framerate <= 0 {
enc.Framerate = 5
}
avWriter, err := mjpeg.New(videoFileName, 1024, 768, enc.Framerate)
if err != nil {
logger.Error("Error during mjpeg init: ", err)
}
enc.avWriter = avWriter
}
func (enc *MJPegImageEncoder) Run() {
}
func (enc *MJPegImageEncoder) Encode(img image.Image) {
buf := &bytes.Buffer{}
jOpts := &jpeg.Options{Quality: enc.Quality}
if enc.Quality <= 0 {
jOpts = nil
}
err := jpeg.Encode(buf, img, jOpts)
if err != nil {
logger.Error("Error while creating jpeg: ", err)
}
err = enc.avWriter.AddFrame(buf.Bytes())
if err != nil {
logger.Error("Error while adding frame to mjpeg: ", err)
}
}
func (enc *MJPegImageEncoder) Close() {
err := enc.avWriter.Close()
if err != nil {
logger.Error("Error while closing mjpeg: ", err)
}
}

92
encoders/x264-enc.go Normal file
View File

@ -0,0 +1,92 @@
package encoders
import (
"image"
"io"
"os"
"os/exec"
"strings"
"vnc2webm/logger"
)
type X264ImageEncoder struct {
cmd *exec.Cmd
binaryPath string
input io.WriteCloser
}
func (enc *X264ImageEncoder) Init(videoFileName string) {
fileExt := ".mp4"
if !strings.HasSuffix(videoFileName, fileExt) {
videoFileName = videoFileName + fileExt
}
//binary := "./ffmpeg"
cmd := exec.Command(enc.binaryPath,
"-f", "image2pipe",
"-vcodec", "ppm",
//"-r", strconv.Itoa(framerate),
"-r", "4",
//"-i", "pipe:0",
"-i", "-",
"-vcodec", "libx264", //"libvpx",//"libvpx-vp9"//"libx264"
"-b:v", "2M",
"-threads", "8",
//"-speed", "0",
//"-lossless", "1", //for vpx
// "-tile-columns", "6",
//"-frame-parallel", "1",
// "-an", "-f", "webm",
"-cpu-used", "-16",
"-preset", "ultrafast",
"-deadline", "realtime",
//"-cpu-used", "-5",
"-maxrate", "2.5M",
"-bufsize", "10M",
"-g", "6",
//"-rc_lookahead", "16",
//"-profile", "0",
"-qmax", "51",
"-qmin", "11",
//"-slices", "4",
//"-vb", "2M",
videoFileName,
)
//cmd := exec.Command("/bin/echo")
//io.Copy(cmd.Stdout, os.Stdout)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
encInput, err := cmd.StdinPipe()
enc.input = encInput
if err != nil {
logger.Error("can't get ffmpeg input pipe")
}
enc.cmd = cmd
}
func (enc *X264ImageEncoder) Run(encoderFilePath string, videoFileName string) {
if _, err := os.Stat(encoderFilePath); os.IsNotExist(err) {
logger.Error("encoder file doesn't exist in path:", encoderFilePath)
return
}
enc.binaryPath = encoderFilePath
enc.Init(videoFileName)
logger.Infof("launching binary: %v", enc.cmd)
err := enc.cmd.Run()
if err != nil {
logger.Errorf("error while launching ffmpeg: %v\n err: %v", enc.cmd.Args, err)
}
}
func (enc *X264ImageEncoder) Encode(img image.Image) {
err := encodePPM(enc.input, img)
if err != nil {
logger.Error("error while encoding image:", err)
}
}
func (enc *X264ImageEncoder) Close() {
}

View File

@ -3,6 +3,7 @@ package vnc2webm
import (
"bytes"
"image"
"image/draw"
"sync"
)
@ -78,6 +79,10 @@ var bPool = sync.Pool{
},
}
type Renderer interface {
SetTargetImage(draw.Image)
}
// Encoding represents interface for vnc encoding
type Encoding interface {
Type() EncodingType

View File

@ -12,8 +12,6 @@ import (
"image/jpeg"
"io"
"math"
"os"
"strconv"
"vnc2webm/logger"
)
@ -79,9 +77,9 @@ func getTightColor(c io.Reader, pf *PixelFormat) (*color.RGBA64, error) {
return nil, err
}
rgb := color.RGBA64{
R: uint16(tbytes[0]), //byte(col >> pf.RedShift & int32(pf.RedMax)),
G: uint16(tbytes[1]), //byte(col >> pf.GreenShift & int32(pf.GreenMax)),
B: uint16(tbytes[2]), //byte(col >> pf.BlueShift & int32(pf.BlueMax)),
R: uint16(tbytes[0]),
G: uint16(tbytes[1]),
B: uint16(tbytes[2]),
A: uint16(1),
}
return &rgb, nil
@ -114,24 +112,9 @@ func getTightColor(c io.Reader, pf *PixelFormat) (*color.RGBA64, error) {
B: uint16((pixel >> pf.BlueShift) & uint32(pf.BlueMax)),
}
// else {
// *clr = clr.cm[pixel]
// clr.cmIndex = pixel
// }
return &rgb, nil
}
// func getTightColor(bytes []byte, pf *PixelFormat) color.RGBA {
// col := (int32(bytes[0])&0xff)<<16 | (int32(bytes[1])&0xff)<<8 | int32(bytes[2])&0xff
// rgb := color.RGBA{
// R: byte(col >> pf.RedShift & int32(pf.RedMax)),
// G: byte(col >> pf.GreenShift & int32(pf.GreenMax)),
// B: byte(col >> pf.BlueShift & int32(pf.BlueMax)),
// A: byte(1),
// }
// return rgb
// }
func calcTightBytePerPixel(pf *PixelFormat) int {
bytesPerPixel := int(pf.BPP / 8)
@ -153,16 +136,19 @@ func (enc *TightEncoding) resetDecoders(compControl uint8) {
}
}
func (enc *TightEncoding) SetTargetImage(img draw.Image) {
enc.Image = img
}
var counter int = 0
func (enc *TightEncoding) Read(c Conn, rect *Rectangle) error {
var out *os.File
var err error
////////////
if counter > 40 {
os.Exit(1)
}
// if counter > 40 {
// os.Exit(1)
// }
////////////
pixelFmt := c.PixelFormat()
bytesPixel := calcTightBytePerPixel(&pixelFmt)
@ -180,15 +166,16 @@ func (enc *TightEncoding) Read(c Conn, rect *Rectangle) error {
compctl, err := ReadUint8(c)
/////////////////
if out == nil {
out, err = os.Create("./output" + strconv.Itoa(counter) + "-" + strconv.Itoa(int(compctl)) + ".jpg")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
defer func() { counter++ }()
defer jpeg.Encode(out, enc.Image, nil)
// var out *os.File
// if out == nil {
// out, err = os.Create("./output" + strconv.Itoa(counter) + "-" + strconv.Itoa(int(compctl)) + ".jpg")
// if err != nil {
// fmt.Println(err)
// os.Exit(1)
// }
// }
// defer func() { counter++ }()
// defer jpeg.Encode(out, enc.Image, nil)
//////////////
if err != nil {
@ -213,15 +200,7 @@ func (enc *TightEncoding) Read(c Conn, rect *Rectangle) error {
return err
}
//logger.Debugf("bytesPixel= %d, compctl= %d, color= %v", bytesPixel, compctl, rectColor)
//imgRect := image.Rect(0, 0, int(c.Width()), int(c.Height()))
// if enc.Image == nil {
// enc.Image = image.NewRGBA(imgRect)
// }
c1 := color.RGBAModel.Convert(rectColor).(color.RGBA)
dst := (enc.Image).(*image.RGBA) // enc.Image.(*image.RGBA)
var x, y int
@ -232,14 +211,8 @@ func (enc *TightEncoding) Read(c Conn, rect *Rectangle) error {
dst.Pix[offset+1] = c1.G
dst.Pix[offset+2] = c1.B
dst.Pix[offset+3] = c1.A
//dst.Set(int(x), int(y), c1)
//dst.Pix[y*uint16(dst.Bounds().Max.Y)+x] = []uint8{rectColor.R, rectColor.G, rectColor.B}
}
}
enc.Image = dst
//draw.Draw(dst, imgRect, &image.Uniform{rectColor}, image.ZP, draw.Src)
if bytesPixel != 3 {
return fmt.Errorf("non tight bytesPerPixel format, should be 3 bytes")
@ -337,27 +310,7 @@ func (enc *TightEncoding) handleTightFilters(compCtl uint8, pixelFmt *PixelForma
}
//logger.Errorf("handleTightFilters: got tight data: %v", tightBytes)
myImg := enc.Image.(draw.Image)
bytePos := 0
bitPos := 0
var palettePos int
for i := 0; i < int(rect.Height); i++ {
for j := 0; j < int(rect.Width); j++ {
if len(palette) == 2 {
currByte := tightBytes[bytePos]
palettePos = int(currByte&byte(math.Pow(2.0, float64(bitPos)))) >> uint(bitPos)
//logger.Debugf("palletPos=%d, bitpos=%d, bytepos=%d", palettePos, bitPos, bytePos)
bytePos = bytePos + int((bitPos+1.0)/8.0)
bitPos = (bitPos + 1) % 8
//logger.Debugf("next: bitpos=%d, bytepos=%d", bitPos, bytePos)
} else {
palettePos = int(tightBytes[bytePos])
bytePos++
}
myImg.Set(int(rect.X)+j, int(rect.Y)+i, palette[palettePos])
//logger.Debugf("(%d,%d): pos: %d col:%d", int(rect.X)+j, int(rect.Y)+i, palettePos, palette[palettePos])
}
}
enc.drawTightPalette(rect, palette, tightBytes)
//enc.Image = myImg
case TightFilterGradient: //GRADIENT_FILTER
logger.Debugf("----GRADIENT_FILTER: bytesPixel=%d, counter=%d", bytesPixel, counter)
@ -387,6 +340,29 @@ func (enc *TightEncoding) handleTightFilters(compCtl uint8, pixelFmt *PixelForma
return
}
func (enc *TightEncoding) drawTightPalette(rect *Rectangle, palette color.Palette, tightBytes []byte) {
myImg := enc.Image.(draw.Image)
bytePos := 0
bitPos := 0
var palettePos int
for i := 0; i < int(rect.Height); i++ {
for j := 0; j < int(rect.Width); j++ {
if len(palette) == 2 {
currByte := tightBytes[bytePos]
palettePos = int(currByte&byte(math.Pow(2.0, float64(bitPos)))) >> uint(bitPos)
//logger.Debugf("palletPos=%d, bitpos=%d, bytepos=%d", palettePos, bitPos, bytePos)
bytePos = bytePos + int((bitPos+1.0)/8.0)
bitPos = (bitPos + 1) % 8
//logger.Debugf("next: bitpos=%d, bytepos=%d", bitPos, bytePos)
} else {
palettePos = int(tightBytes[bytePos])
bytePos++
}
myImg.Set(int(rect.X)+j, int(rect.Y)+i, palette[palettePos])
//logger.Debugf("(%d,%d): pos: %d col:%d", int(rect.X)+j, int(rect.Y)+i, palettePos, palette[palettePos])
}
}
}
func (enc *TightEncoding) decodeGradData(rect *Rectangle, buffer []byte) {
logger.Debugf("putting gradient size: %v on image: %v", rect, enc.Image.Bounds())

View File

@ -3,11 +3,11 @@ package main
import (
"context"
"image"
"log"
"net"
"os"
"time"
vnc "vnc2webm"
"vnc2webm/encoders"
"vnc2webm/logger"
)
@ -16,7 +16,7 @@ func main() {
// Establish TCP connection to VNC server.
nc, err := net.DialTimeout("tcp", os.Args[1], 5*time.Second)
if err != nil {
log.Fatalf("Error connecting to VNC host. %v", err)
logger.Fatalf("Error connecting to VNC host. %v", err)
}
logger.Debugf("starting up the client, connecting to: %s", os.Args[1])
@ -41,31 +41,54 @@ func main() {
cc, err := vnc.Connect(context.Background(), nc, ccfg)
if err != nil {
log.Fatalf("Error negotiating connection to VNC host. %v", err)
logger.Fatalf("Error negotiating connection to VNC host. %v", err)
}
// out, err := os.Create("./output" + strconv.Itoa(counter) + ".jpg")
// if err != nil {
// fmt.Println(err)
// os.Exit(1)
// }
//vcodec := &encoders.MJPegImageEncoder{Quality: 60, Framerate: 6}
vcodec := &encoders.X264ImageEncoder{}
counter := 0
//vcodec.Init("./output" + strconv.Itoa(counter))
go vcodec.Run("./ffmpeg", "./output.mp4")
screenImage := image.NewRGBA(image.Rect(0, 0, int(cc.Width()), int(cc.Height())))
for _, enc := range ccfg.Encodings {
myRenderer, ok := enc.(vnc.Renderer)
if ok {
myRenderer.SetTargetImage(screenImage)
}
}
// var out *os.File
logger.Debugf("connected to: %s", os.Args[1])
defer cc.Close()
cc.SetEncodings([]vnc.EncodingType{vnc.EncTight})
rect := image.Rect(0, 0, int(cc.Width()), int(cc.Height()))
screenImage := image.NewRGBA64(rect)
//rect := image.Rect(0, 0, int(cc.Width()), int(cc.Height()))
//screenImage := image.NewRGBA64(rect)
// Process messages coming in on the ServerMessage channel.
for {
select {
case err := <-errorCh:
panic(err)
case msg := <-cchClient:
log.Printf("Received client message type:%v msg:%v\n", msg.Type(), msg)
logger.Debugf("Received client message type:%v msg:%v\n", msg.Type(), msg)
case msg := <-cchServer:
log.Printf("Received server message type:%v msg:%v\n", msg.Type(), msg)
myRenderer, ok := msg.(vnc.Renderer)
logger.Debugf("Received server message type:%v msg:%v\n", msg.Type(), msg)
if ok {
err = myRenderer.Render(screenImage)
if err != nil {
log.Printf("Received server message type:%v msg:%v\n", msg.Type(), msg)
}
}
// out, err := os.Create("./output" + strconv.Itoa(counter) + ".jpg")
// if err != nil {
// fmt.Println(err)
// os.Exit(1)
// }
counter++
//jpeg.Encode(out, screenImage, nil)
vcodec.Encode(screenImage)
reqMsg := vnc.FramebufferUpdateRequest{Inc: 1, X: 0, Y: 0, Width: cc.Width(), Height: cc.Height()}
reqMsg.Write(cc)
}

View File

@ -6,7 +6,6 @@ import (
"encoding/base64"
"fmt"
"io"
"log"
"net"
"net/http"
_ "net/http/pprof"
@ -15,6 +14,7 @@ import (
"sync"
"time"
vnc "vnc2webm"
"vnc2webm/logger"
)
type Auth struct {
@ -189,7 +189,7 @@ func (auth *AuthVNCHTTP) Auth(c vnc.Conn) error {
if err != nil {
return fmt.Errorf("failed to get auth data: %s", err.Error())
}
log.Printf("http auth: %s\n", buf.Bytes())
logger.Infof("http auth: %s\n", buf.Bytes())
res.Body.Close()
data := strings.Split(buf.String(), " ")
if len(data) < 2 {
@ -223,12 +223,12 @@ func (*AuthVNCHTTP) SubType() vnc.SecuritySubType {
func main() {
go func() {
log.Println(http.ListenAndServe(":6060", nil))
logger.Info(http.ListenAndServe(":6060", nil))
}()
ln, err := net.Listen("tcp", ":6900")
if err != nil {
log.Fatalf("Error listen. %v", err)
logger.Fatalf("Error listen. %v", err)
}
schClient := make(chan vnc.ClientMessage)

View File

@ -4,17 +4,17 @@ import (
"context"
"fmt"
"image"
"log"
"math"
"net"
"time"
vnc "vnc2webm"
"vnc2webm/logger"
)
func main() {
ln, err := net.Listen("tcp", ":5900")
if err != nil {
log.Fatalf("Error listen. %v", err)
logger.Fatalf("Error listen. %v", err)
}
chServer := make(chan vnc.ClientMessage)
@ -23,7 +23,7 @@ func main() {
im := image.NewRGBA(image.Rect(0, 0, width, height))
tick := time.NewTicker(time.Second / 2)
defer tick.Stop()
cfg := &vnc.ServerConfig{
Width: 800,
Height: 600,
@ -50,12 +50,12 @@ func main() {
case msg := <-chClient:
switch msg.Type() {
default:
log.Printf("11 Received message type:%v msg:%v\n", msg.Type(), msg)
logger.Debugf("11 Received message type:%v msg:%v\n", msg.Type(), msg)
}
case msg := <-chServer:
switch msg.Type() {
default:
log.Printf("22 Received message type:%v msg:%v\n", msg.Type(), msg)
logger.Debugf("22 Received message type:%v msg:%v\n", msg.Type(), msg)
}
}
}

View File

@ -2,7 +2,7 @@ package logger
import "fmt"
var simpleLogger = SimpleLogger{LogLevelDebug}
var simpleLogger = SimpleLogger{LogLevelInfo}
type Logger interface {
Debug(v ...interface{})

View File

@ -3,7 +3,6 @@ package vnc2webm
import (
"encoding/binary"
"fmt"
"image/draw"
"vnc2webm/logger"
)
@ -83,10 +82,6 @@ type ServerMessage interface {
Supported(Conn) bool
}
type Renderer interface {
Render(draw.Image) error
}
// FramebufferUpdate holds a FramebufferUpdate wire format message.
type FramebufferUpdate struct {
_ [1]byte // pad
@ -99,11 +94,6 @@ func (msg *FramebufferUpdate) String() string {
return fmt.Sprintf("rects %d rectangle[]: { %v }", msg.NumRect, msg.Rects)
}
func (msg *FramebufferUpdate) Render(draw.Image) error {
return nil
}
func (msg *FramebufferUpdate) Supported(c Conn) bool {
return true
}