Skip to content

StreamResponse, MultipartWriter with multipart/x-mixed-replace not working #3104

Closed
@james4388

Description

Long story short

StreamResponse MultipartWriter with multipart/x-mixed-replace not working
(MJpeg Streaming Protocol)

Expected behaviour

Streamable jpeg series

Actual behaviour

Image not loading

Steps to reproduce

This is server code I use

import asyncio
import cv2
from aiohttp import web, MultipartWriter


async def mjpeg_handler(request):
    my_boundary = "boundarydonotcross"
    response = web.StreamResponse(status=200, reason='OK', headers={
        'Content-Type': 'multipart/x-mixed-replace; '
                        'boundary=--%s' % my_boundary,
    })
    await response.prepare(request)
    wc = cv2.VideoCapture(0)
    encode_param = (int(cv2.IMWRITE_JPEG_QUALITY), 90)

    while True:
        _, frame = wc.read()
        if frame is None:
            continue
        with MultipartWriter('image/jpeg', boundary=my_boundary) as mpwriter:
            result, encimg = cv2.imencode('.jpg', frame, encode_param)
            data = encimg.tostring()
            mpwriter.append(data, {
                'Content-Type': 'image/jpeg'
            })
            await mpwriter.write(response)
        await response.drain()
    wc.shutdown()
    return response


async def index(request):
    return web.Response(text='<img src="/image"/>', content_type='text/html')


async def start_server(loop, address, port):
    app = web.Application(loop=loop)
    app.router.add_route('GET', "/", index)
    app.router.add_route('GET', "/image", mjpeg_handler)
    return await loop.create_server(app.make_handler(), address, port)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(start_server(loop, '0.0.0.0', 8888))
    print("Server ready!")

    try:
        loop.run_forever()
    except KeyboardInterrupt:
        print("Shutting Down!")
        loop.close()

When I inspect return stream with curl I see that boundary is being send twice, one for open, one for closing

--boundary
<data>
--boundary--

which is correct with w3c spec but that break the mjpeg

I try to comment await writer.write(b'--' + self._boundary + b'--\r\n') in multipart.py line 821 and it works fine.

This is my manual workaround and it works

async def mjpeg_handler(request):
    boundary = "boundarydonotcross"
    response = web.StreamResponse(status=200, reason='OK', headers={
        'Content-Type': 'multipart/x-mixed-replace; '
                        'boundary=--%s' % boundary,
    })
    await response.prepare(request)
    wc = cv2.VideoCapture(0)
    encode_param = (int(cv2.IMWRITE_JPEG_QUALITY), 90)

    while True:
        _, frame = wc.read()
        if frame is None:
            continue
        
        result, encimg = cv2.imencode('.jpg', frame, encode_param)
        data = encimg.tostring()
        await response.write(
            '--{}\r\n'.format(boundary).encode('utf-8'))
        await response.write(b'Content-Type: image/jpeg\r\n')
        await response.write('Content-Length: {}\r\n'.format(
                len(data)).encode('utf-8'))
        await response.write(b"\r\n")
        # Write data
        await response.write(data)
        await response.write(b"\r\n")
        await response.drain()
    wc.shutdown()
    return response

Your environment

aiohttp server 3.3.2
raspberry pi
cv2 (OpenCV3)

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions