Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance of JPEG decode/re-encode #6

Closed
alex-nightingale opened this issue Sep 13, 2021 · 4 comments
Closed

Performance of JPEG decode/re-encode #6

alex-nightingale opened this issue Sep 13, 2021 · 4 comments

Comments

@alex-nightingale
Copy link

I'm having a little trouble with the performance of the library - my use-case is more-or-less exactly as [_example/mjpegproxy.go] - I'm reading several MJPEG streams and re-serving them. Looking at the code I notice that it effectively reads the JPEG bytes from each part, decodes that into an image.Image, then turns that image.Image back into bytes, which seems very inefficient?

I've tried skipping the JPEG decode -> encode and just writing the bytes from the part straight to the stream, but the output comes out very broken. I'd love to find a way to achieve this without needing the decode/encode step as I'm finding that's putting a lot of pressure on the CPU with multiple streams - I'm seeing it take on average between 50ms and 80ms per part, which isn't great for a 25fps stream.

I'll open a PR if I figure out a way to handle this, but otherwise I'd love to know what the decode->encode is doing under the hood, because it's clearly necessary!

@mattn
Copy link
Owner

mattn commented Sep 25, 2021

How about to add DecodeRaw ?

@mattn
Copy link
Owner

mattn commented Sep 25, 2021

Could you please try this patch?

diff --git a/_example/mjpegproxy.go b/_example/mjpegproxy.go
index 4186dff..65db818 100644
--- a/_example/mjpegproxy.go
+++ b/_example/mjpegproxy.go
@@ -1,12 +1,11 @@
+//go:build ignore
 // +build ignore
 
 package main
 
 import (
-	"bytes"
 	"context"
 	"flag"
-	"image/jpeg"
 	"log"
 	"net/http"
 	"os"
@@ -31,18 +30,12 @@ func proxy(wg *sync.WaitGroup, stream *mjpeg.Stream) {
 		log.Fatal(err)
 	}
 
-	var buf bytes.Buffer
 	for {
-		img, err := dec.Decode()
+		b, err := dec.DecodeRaw()
 		if err != nil {
 			break
 		}
-		buf.Reset()
-		err = jpeg.Encode(&buf, img, nil)
-		if err != nil {
-			break
-		}
-		err = stream.Update(buf.Bytes())
+		err = stream.Update(b)
 		if err != nil {
 			break
 		}
diff --git a/mjpeg.go b/mjpeg.go
index b9b2c93..c60509f 100644
--- a/mjpeg.go
+++ b/mjpeg.go
@@ -1,6 +1,7 @@
 package mjpeg
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"image"
@@ -59,6 +60,20 @@ func (d *Decoder) Decode() (image.Image, error) {
 	return jpeg.Decode(p)
 }
 
+// DecodeRaw do decoding raw bytes
+func (d *Decoder) DecodeRaw() ([]byte, error) {
+	p, err := d.r.NextPart()
+	if err != nil {
+		return nil, err
+	}
+	var buf bytes.Buffer
+	_, err = io.Copy(&buf, p)
+	if err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
 type Stream struct {
 	m        sync.Mutex
 	s        map[chan []byte]struct{}

@mattn
Copy link
Owner

mattn commented Sep 25, 2021

Or one another patch.

diff --git a/_example/mjpegproxy.go b/_example/mjpegproxy.go
index 4186dff..7f7824b 100644
--- a/_example/mjpegproxy.go
+++ b/_example/mjpegproxy.go
@@ -1,12 +1,12 @@
+//go:build ignore
 // +build ignore
 
 package main
 
 import (
-	"bytes"
 	"context"
 	"flag"
-	"image/jpeg"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -31,22 +31,7 @@ func proxy(wg *sync.WaitGroup, stream *mjpeg.Stream) {
 		log.Fatal(err)
 	}
 
-	var buf bytes.Buffer
-	for {
-		img, err := dec.Decode()
-		if err != nil {
-			break
-		}
-		buf.Reset()
-		err = jpeg.Encode(&buf, img, nil)
-		if err != nil {
-			break
-		}
-		err = stream.Update(buf.Bytes())
-		if err != nil {
-			break
-		}
-	}
+	io.Copy(stream, dec)
 }
 
 func main() {
diff --git a/mjpeg.go b/mjpeg.go
index b9b2c93..57dbdff 100644
--- a/mjpeg.go
+++ b/mjpeg.go
@@ -1,6 +1,7 @@
 package mjpeg
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"image"
@@ -59,6 +60,20 @@ func (d *Decoder) Decode() (image.Image, error) {
 	return jpeg.Decode(p)
 }
 
+// DecodeRaw do decoding raw bytes
+func (d *Decoder) DecodeRaw() ([]byte, error) {
+	p, err := d.r.NextPart()
+	if err != nil {
+		return nil, err
+	}
+	var buf bytes.Buffer
+	_, err = io.Copy(&buf, p)
+	if err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
 type Stream struct {
 	m        sync.Mutex
 	s        map[chan []byte]struct{}
@@ -95,6 +110,10 @@ func (s *Stream) Close() error {
 	return nil
 }
 
+func (s *Stream) Write(b []byte) (int, error) {
+	return len(b), s.Update(b)
+}
+
 func (s *Stream) Update(b []byte) error {
 	s.m.Lock()
 	defer s.m.Unlock()

This should be good than previous.

@mattn mattn closed this as completed in b432a0c Sep 26, 2021
@mattn
Copy link
Owner

mattn commented Sep 26, 2021

Added DecodeRaw. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants