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

JPEG Camera Streaming #1173

Closed
MAkcanca opened this issue Mar 23, 2018 · 9 comments
Closed

JPEG Camera Streaming #1173

MAkcanca opened this issue Mar 23, 2018 · 9 comments

Comments

@MAkcanca
Copy link

MAkcanca commented Mar 23, 2018

Probably due to my python knowledge level, I can't make it work with Sanic. I had this piece of code when I was on Flask :

def gen(camera):
    """Video streaming generator function."""
    while True:
        frame = camera.get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')


@app.route('/video_feed')
def video_feed():
    """Video streaming route. Put this in the src attribute of an img tag."""
    return Response(gen(Camera()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

The problem is, I can't use plain "response" function to replace "return Response" because it has no mimetype or content_type. There's a stream function in sanic but whenever I use it , I get this TypeError: 'generator' object is not callable error and I don't know how to flip the boards. Any ideas? Thank you

P.S: Sorry, I know you guys are busy with engineering problems but I have been baffling with this for a week now. I could really use some ideas. Thank you.

@erodozer
Copy link

Streaming in Sanic takes a function that can write directly to the response, not a generator. So you'd probably need to structure your code like

from sanic import response
import asyncio

# hard-coded fps, 
#   would probably be best to use 
#   the camera's approx supported fps instead
FPS = 29.97
STREAM_RESPONSE = \
"""
--frame
Content-Type: image/jpeg

"""

@app.route('/video_feed')
async def video_feed(request):
    """Video streaming route. Put this in the src attribute of an img tag."""
    async def stream_camera(response):
        camera = Camera()
        while True:
            frame = camera.get_frame()
            response.write(STREAM_RESPONSE + frame)
            await asyncio.sleep(1.0 / FPS)

    return response.stream(
                stream_camera,
                content_type='multipart/x-mixed-replace; boundary=frame'
            )

@MAkcanca
Copy link
Author

If anyone is wondering, I solved it with this code

async def gen(camera, response):
    """Video streaming generator function."""
    loop = asyncio.get_event_loop()
    while True:
        frame = await loop.run_in_executor(None, camera.get_frame)
        response.write(b'--frame\r\n'
                       b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
        await asyncio.sleep(1.0/FPS)

@app.route('/video_feed')
def video_feed(request):
    """Video streaming route. Put this in the src attribute of an img tag."""
    return stream(partial(gen, Camera()),
                    content_type='multipart/x-mixed-replace; boundary=frame')

Although, the code before this comment by @nhydock works too. Thank you!

@erodozer
Copy link

In sanic you should be getting the event loop from the request object just to be safe request.app.loop.

You might actually want to run your camera frame fetching in an async task on sanic as described in the Middleware docs and store the frame in memory, then have your streams fetch the stored frame instead of make a request to the camera. That way you don't have too many IO connections open to it, which can improve throughput.

Glad to see that you are able to stream data from your camera, though, that's pretty cool.

@MAkcanca
Copy link
Author

@nhydock My code hangs after 5-10 seconds. Any idea why and how can I solve it?
Also where do we get the request object from?
I read the documentation but I couldn't make it work, how do I store the frame on memory. Can I have some examples?

Thank you so much.

@asvetlov
Copy link
Contributor

@nhydock as of Python 3.5.3 request.app.loop is the same object as asyncio.get_event_loop(), isnt't it?

Anyway, respose.write() is a sync call, it means that if a socket peer didn't consume the data more often than sanic sends it -- MemoryError will catch you very soon.
Correct me if I'm wrong please.

@erodozer
Copy link

ah, sorry, that was my misunderstanding from looking at the deployment docs, which state

loop (default None): An asyncio-compatible event loop. If none is specified, Sanic creates its own event loop.

The api docs/code say it's the same as asyncio.get_event_loop()

Anyway, streaming problems with synchronous write would be related to #1067

@MAkcanca
Copy link
Author

MAkcanca commented Mar 23, 2018

@asvetlov I'm not able to see any errors, it hungs up the system and my remote wi-fi connection gets interrupted until the program closes itself or I reboot it. But I suspect it's a memory error too.

@nhydock I checked it out before, but couldn't figure out how to implement it on my case. It's basically splitting the data on chunks if I got it right, but I don't have any file to split.

Thank you so much for your efforts

@stale
Copy link

stale bot commented May 14, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is incorrect, please respond with an update. Thank you for your contributions.

@stale stale bot added the stale label May 14, 2019
@andreymal
Copy link
Contributor

It's probably fixed by #1179

@stale stale bot removed the stale label May 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants