summaryrefslogtreecommitdiffstats
path: root/multimedia/gst1-rtsp-server/tests/transport.py
blob: ed66f30ec6fae99c1fdfdac7e42a56f9c66ecb1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
import json
import os
from pathlib import Path
import subprocess

import gi

gi.require_version('Gst', '1.0')
gi.require_version('GstApp', '1.0')
gi.require_version('GstRtspServer', '1.0')
from gi.repository import GLib, Gst, GstRtspServer

Gst.init(None)
TEST_TIMEOUT_SECONDS = 15
module_path = Path(__file__).resolve().parents[1] / 'files' / 'model.uc'


def model_call(function, options):
    script = (
        f'import {{ {function} }} from {json.dumps(str(module_path))}; '
        f'print({function}(json(ARGV[0]), {{}}, {{}}));'
    )
    return subprocess.check_output(
        [os.environ.get('UCODE', 'ucode'), '-e', script, json.dumps(options)],
        text=True,
    )


def codec_check(codec, encoder, depayloader):
    loop = GLib.MainLoop()
    pipelines = []
    errors = []
    frames = []

    def message_receive(bus, message):
        if message.type == Gst.MessageType.ERROR:
            errors.append(str(message.parse_error()))
            loop.quit()

    def sample_receive(sink):
        sample = sink.emit('pull-sample')
        frames.append(sample.get_caps().to_string())
        if len(frames) >= 3:
            GLib.idle_add(loop.quit)
        return Gst.FlowReturn.OK

    def pipeline_start(launch, observer=False):
        pipeline = Gst.parse_launch(launch)
        if observer:
            pipeline.get_by_name('observer').connect('new-sample', sample_receive)
        bus = pipeline.get_bus()
        bus.add_signal_watch()
        bus.connect('message', message_receive)
        pipelines.append(pipeline)
        assert pipeline.set_state(Gst.State.PLAYING) != Gst.StateChangeReturn.FAILURE

    def timeout_fail():
        errors.append(f'{codec}: RTSP test exceeded {TEST_TIMEOUT_SECONDS} seconds')
        loop.quit()
        return GLib.SOURCE_REMOVE

    server = GstRtspServer.RTSPServer()
    server.set_address('127.0.0.1')
    server.set_service('0')
    factory = GstRtspServer.RTSPMediaFactory()
    stream_name = f'test_{os.getpid()}_{codec}'
    factory.set_launch(model_call('receiver_build', {'.name': stream_name, 'codec': codec}))
    factory.set_shared(True)
    server.get_mount_points().add_factory('/test', factory)
    source = server.attach(None)
    assert source, 'Cannot bind RTSP listener'
    options = {
        'source': 'videotestsrc',
        'source_properties': 'is-live=true name="camera with  two spaces"',
        'codec': codec,
        'encoder': encoder,
        'width': 320,
        'height': 240,
        'framerate': '30/1',
        'pixel_format': 'I420',
    }
    if codec != 'mjpeg':
        options['keyframe_interval'] = 30
    producer = model_call('source_build', options)
    producer += ' ! ' + model_call('encoding_build', options)
    producer += ' ! ' + model_call('sender_build', {'.name': stream_name})
    command = json.loads(model_call('launch_argv', producer))
    command[command.index('-q')] = '-m'
    capture = subprocess.Popen(command, stdout=subprocess.PIPE, text=True)
    for message in capture.stdout:
        if ('from element "pipeline0" (state-changed)' in message and
                'new-state=(GstState)playing' in message):
            break
    else:
        raise AssertionError(f'{codec}: capture did not enter PLAYING')
    pipeline_start(
        f'rtspsrc location=rtsp://127.0.0.1:{server.get_bound_port()}/test '
        f'protocols=tcp latency=0 ! {depayloader} ! decodebin ! '
        'appsink name=observer emit-signals=true sync=false',
        observer=True,
    )
    timeout = GLib.timeout_add_seconds(TEST_TIMEOUT_SECONDS, timeout_fail)
    loop.run()
    if not errors:
        GLib.source_remove(timeout)
    for pipeline in reversed(pipelines):
        pipeline.set_state(Gst.State.NULL)
        pipeline.get_bus().remove_signal_watch()
    capture.terminate()
    capture_status = capture.wait()
    capture.stdout.close()
    GLib.source_remove(source)
    assert not errors, errors
    assert capture_status in (0, -15), capture_status
    assert len(frames) >= 3, frames
    assert all('width=(int)320' in caps and 'height=(int)240' in caps for caps in frames)
    print(f'{codec}: generated Unix FD handoff and RTSP client decoding passed')


for codec, encoder, depayloader in [
    ('mjpeg', 'jpegenc', 'rtpjpegdepay'),
    ('h264', 'openh264enc', 'rtph264depay'),
    ('h265', 'x265enc', 'rtph265depay'),
]:
    codec_check(codec, encoder, depayloader)