diff --git a/encoders/dv8-enc.go b/encoders/dv8-enc.go index 3c54622..3395e97 100644 --- a/encoders/dv8-enc.go +++ b/encoders/dv8-enc.go @@ -14,10 +14,14 @@ type VP8ImageEncoder struct { FFMpegBinPath string input io.WriteCloser closed bool + Framerate int } func (enc *VP8ImageEncoder) Init(videoFileName string) { fileExt := ".webm" + if enc.Framerate == 0 { + enc.Framerate = 12 + } if !strings.HasSuffix(videoFileName, fileExt) { videoFileName = videoFileName + fileExt } diff --git a/encoders/dv9-enc.go b/encoders/dv9-enc.go index c608789..c5e4016 100644 --- a/encoders/dv9-enc.go +++ b/encoders/dv9-enc.go @@ -13,10 +13,14 @@ type DV9ImageEncoder struct { cmd *exec.Cmd FFMpegBinPath string input io.WriteCloser + Framerate int } func (enc *DV9ImageEncoder) Init(videoFileName string) { fileExt := ".mp4" + if enc.Framerate == 0 { + enc.Framerate = 12 + } if !strings.HasSuffix(videoFileName, fileExt) { videoFileName = videoFileName + fileExt } diff --git a/encoders/huffyuv-enc.go b/encoders/huffyuv-enc.go new file mode 100644 index 0000000..8ec3651 --- /dev/null +++ b/encoders/huffyuv-enc.go @@ -0,0 +1,116 @@ +package encoders + +import ( + "errors" + "image" + "io" + "os" + "os/exec" + "strings" + "vnc2video/logger" +) + +// this is a very common loseless encoder (but produces huge files) +type HuffYuvImageEncoder struct { + FFMpegBinPath string + cmd *exec.Cmd + input io.WriteCloser + closed bool + Framerate int +} + +func (enc *HuffYuvImageEncoder) Init(videoFileName string) { + if enc.Framerate == 0 { + enc.Framerate = 12 + } + + fileExt := ".avi" + if !strings.HasSuffix(videoFileName, fileExt) { + videoFileName = videoFileName + fileExt + } + //binary := "./ffmpeg" + cmd := exec.Command(enc.FFMpegBinPath, + "-f", "image2pipe", + "-vcodec", "ppm", + //"-r", strconv.Itoa(framerate), + "-r", "12", + + //"-re", + //"-i", "pipe:0", + "-an", //no audio + //"-vsync", "2", + ///"-probesize", "10000000", + "-y", + + "-i", "-", + //"–s", "640×360", + "-vcodec", "huffyuv", //"libvpx",//"libvpx-vp9"//"libx264" + //"-b:v", "0.33M", + "-threads", "7", + ///"-coder", "1", + ///"-bf", "0", + ///"-me_method", "hex", + //"-speed", "0", + //"-lossless", "1", //for vpx + // "-an", "-f", "webm", + "-preset", "veryfast", + //"-tune", "animation", + "-maxrate", "0.5M", + "-bufsize", "50M", + "-g", "250", + + //"-crf", "0", //for lossless encoding!!!! + + //"-rc_lookahead", "16", + //"-profile", "0", + "-crf", "34", + //"-qmax", "51", + //"-qmin", "7", + //"-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 *HuffYuvImageEncoder) Run(videoFileName string) error { + if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) { + logger.Error("encoder file doesn't exist in path:", enc.FFMpegBinPath) + return errors.New("encoder file doesn't exist in path" + videoFileName) + } + + enc.Init(videoFileName) + logger.Debugf("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) + return err + } + return nil +} +func (enc *HuffYuvImageEncoder) Encode(img image.Image) { + if enc.input == nil || enc.closed { + return + } + + err := encodePPM(enc.input, img) + if err != nil { + logger.Error("error while encoding image:", err) + } +} + +func (enc *HuffYuvImageEncoder) Close() { + enc.closed = true + //enc.cmd.Process.Kill() +} diff --git a/encoders/mjpeg-enc.go b/encoders/mjpeg-enc.go index ea6fdfd..595d58e 100644 --- a/encoders/mjpeg-enc.go +++ b/encoders/mjpeg-enc.go @@ -19,6 +19,9 @@ type MJPegImageEncoder struct { func (enc *MJPegImageEncoder) Init(videoFileName string) { fileExt := ".avi" + if enc.Framerate == 0 { + enc.Framerate = 12 + } if !strings.HasSuffix(videoFileName, fileExt) { videoFileName = videoFileName + fileExt } diff --git a/encoders/qtrle-enc.go b/encoders/qtrle-enc.go new file mode 100644 index 0000000..476eb39 --- /dev/null +++ b/encoders/qtrle-enc.go @@ -0,0 +1,115 @@ +package encoders + +import ( + "errors" + "image" + "io" + "os" + "os/exec" + "strings" + "vnc2video/logger" +) + +// QTRLEImageEncoder quick time rle is an efficient loseless codec, uses .mov extension +type QTRLEImageEncoder struct { + FFMpegBinPath string + cmd *exec.Cmd + input io.WriteCloser + closed bool + Framerate int +} + +func (enc *QTRLEImageEncoder) Init(videoFileName string) { + fileExt := ".mov" + if enc.Framerate == 0 { + enc.Framerate = 12 + } + if !strings.HasSuffix(videoFileName, fileExt) { + videoFileName = videoFileName + fileExt + } + //binary := "./ffmpeg" + cmd := exec.Command(enc.FFMpegBinPath, + "-f", "image2pipe", + "-vcodec", "ppm", + //"-r", strconv.Itoa(framerate), + "-r", "12", + + //"-re", + //"-i", "pipe:0", + "-an", //no audio + //"-vsync", "2", + ///"-probesize", "10000000", + "-y", + + "-i", "-", + //"–s", "640×360", + "-vcodec", "qtrle", //"libvpx",//"libvpx-vp9"//"libx264" + //"-b:v", "0.33M", + "-threads", "7", + ///"-coder", "1", + ///"-bf", "0", + ///"-me_method", "hex", + //"-speed", "0", + //"-lossless", "1", //for vpx + // "-an", "-f", "webm", + "-preset", "veryfast", + //"-tune", "animation", + "-maxrate", "0.5M", + "-bufsize", "50M", + "-g", "250", + + //"-crf", "0", //for lossless encoding!!!! + + //"-rc_lookahead", "16", + //"-profile", "0", + "-crf", "34", + //"-qmax", "51", + //"-qmin", "7", + //"-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 *QTRLEImageEncoder) Run(videoFileName string) error { + if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) { + logger.Error("encoder file doesn't exist in path:", enc.FFMpegBinPath) + return errors.New("encoder file doesn't exist in path" + videoFileName) + } + + enc.Init(videoFileName) + logger.Debugf("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) + return err + } + return nil +} +func (enc *QTRLEImageEncoder) Encode(img image.Image) { + if enc.input == nil || enc.closed { + return + } + + err := encodePPM(enc.input, img) + if err != nil { + logger.Error("error while encoding image:", err) + } +} + +func (enc *QTRLEImageEncoder) Close() { + enc.closed = true + //enc.cmd.Process.Kill() +} diff --git a/encoders/x264-enc.go b/encoders/x264-enc.go index 02f98d2..12db9a5 100644 --- a/encoders/x264-enc.go +++ b/encoders/x264-enc.go @@ -15,10 +15,14 @@ type X264ImageEncoder struct { cmd *exec.Cmd input io.WriteCloser closed bool + Framerate int } func (enc *X264ImageEncoder) Init(videoFileName string) { fileExt := ".mp4" + if enc.Framerate == 0 { + enc.Framerate = 12 + } if !strings.HasSuffix(videoFileName, fileExt) { videoFileName = videoFileName + fileExt } diff --git a/encoding_tight.go b/encoding_tight.go index 0082815..7a1b4e2 100644 --- a/encoding_tight.go +++ b/encoding_tight.go @@ -367,9 +367,8 @@ func (enc *TightEncoding) drawTightPalette(rect *Rectangle, palette color.Palett enc.Image.Set(int(rect.X)+x, int(rect.Y)+y, palette[palettePos]) //logger.Tracef("(%d,%d): pos: %d col:%d", int(rect.X)+j, int(rect.Y)+i, palettePos, palette[palettePos]) } - // if bitPos != 7 { - // bytePos++ - // } + + // reset bit alignment to first bit in byte (msb) bitPos = 7 } diff --git a/encoding_util.go b/encoding_util.go index 252d1de..9c5010c 100644 --- a/encoding_util.go +++ b/encoding_util.go @@ -3,12 +3,18 @@ package vnc2video import ( "encoding/binary" "errors" + "fmt" "image" "image/color" "image/draw" "io" ) +const ( + BlockWidth = 16 + BlockHeight = 16 +) + type VncCanvas struct { draw.Image //DisplayBuff draw.Image @@ -20,6 +26,7 @@ type VncCanvas struct { CursorOffset *image.Point CursorLocation *image.Point DrawCursor bool + Changed map[string]bool } func NewVncCanvas(width, height int) *VncCanvas { @@ -33,6 +40,23 @@ func NewVncCanvas(width, height int) *VncCanvas { return &canvas } +func (c *VncCanvas) SetChanged(rect *Rectangle) { + if c.Changed == nil { + c.Changed = make(map[string]bool) + } + for x := int(rect.X) / BlockWidth; x*BlockWidth < int(rect.X+rect.Width); x++ { + for y := int(rect.Y) / BlockHeight; y*BlockHeight < int(rect.Y+rect.Height); y++ { + key := fmt.Sprintf("%d,%d", x, y) + //fmt.Println("setting block: ", key) + c.Changed[key] = true + } + } +} + +func (c *VncCanvas) Reset(rect *Rectangle) { + c.Changed = nil +} + func (c *VncCanvas) RemoveCursor() image.Image { if c.Cursor == nil || c.CursorLocation == nil { return c.Image diff --git a/encoding_util_test.go b/encoding_util_test.go new file mode 100644 index 0000000..28e8b21 --- /dev/null +++ b/encoding_util_test.go @@ -0,0 +1,15 @@ +package vnc2video + +import "testing" + +func TestSetChanged(t *testing.T) { + canvas := &VncCanvas{} + rect := &Rectangle{X: 1, Y: 1, Width: 1024, Height: 64} + canvas.SetChanged(rect) + if canvas.Changed["64,0"] == false || + canvas.Changed["64,1"] == false || + canvas.Changed["64,4"] == false { + t.Fail() + } + +}