.. _MQA: Media Quality Assessment (MQA) =============================== What is MQA? ------------ Media Quality Assessment is an emerging extension to the CMSD specification (CTA-5006) being developed within the SVTA Measurement/QoE Working Group. The core idea: the encoder already computes perceptual quality metrics as a by-product of compression. MQA makes those metrics available in CMSD headers so they travel with the content through the delivery chain — from the encoder, through CDN caches, to the player and analytics systems — without requiring separate hardware probes or post-encode analysis passes. CDN routing, ABR decisions, and monitoring dashboards can all act on quality signals that are intrinsic to the stream. Scout implements the baseline MQA metrics (PSNR and SSIM) with optional VMAF support. MQA is **per-stream** and **opt-in** — there is no compute cost for streams that do not have it enabled. Configuration ------------- Plugin-level settings (``scout.properties``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. list-table:: :widths: 35 50 15 :header-rows: 1 * - Key - Description - Default * - ``cmsdVmafModelPath`` - Absolute path to a custom libvmaf ``.json`` model file. Leave blank to use the built-in ``vmaf_v0.6.1`` model. Only relevant when libvmaf is present in the bundled FFmpeg. - (built-in model) .. code-block:: yaml # scout.properties cmsdVmafModelPath: "" # or e.g. /usr/local/share/vmaf/vmaf_4k_v0.6.1.json Per-stream settings (REST API) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ MQA flags are configured per stream, not globally, so operators can enable quality metrics only on the streams that need them. .. list-table:: :widths: 35 50 15 :header-rows: 1 * - Field - Description - Default * - ``cmsdMqaEnabled`` - Enable baseline MQA (PSNR + SSIM) for this stream. - ``false`` * - ``cmsdVmafEnabled`` - Additionally compute VMAF. Ignored (with a log warning) if libvmaf is not present in the bundled FFmpeg. - ``false`` * - ``cmsdVmafSampleInterval`` - Compute VMAF every *N* keyframes. ``1`` = every keyframe (default). Higher values reduce CPU overhead at the cost of coarser per-segment VMAF granularity. - ``1`` REST API -------- Get MQA configuration for a stream ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash GET /rest/scout/stream/{appName}/{streamId}/mqa Example:: curl http://localhost:5080/rest/scout/stream/LiveApp/myStream/mqa Response:: { "streamId": "myStream", "cmsdMqaEnabled": false, "cmsdVmafEnabled": false, "cmsdVmafSampleInterval": 1, "vmafLibAvailable": false } The ``vmafLibAvailable`` field shows whether the bundled FFmpeg was compiled with ``--enable-libvmaf``. This is determined at plugin startup and cannot be changed without swapping the native FFmpeg libraries. Set MQA configuration for a stream ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash PUT /rest/scout/stream/{appName}/{streamId}/mqa Accepted body fields (all optional; missing fields are left unchanged): .. code-block:: bash # Enable PSNR + SSIM only curl -X PUT http://localhost:5080/rest/scout/stream/LiveApp/myStream/mqa \ -H "Content-Type: application/json" \ -d '{"cmsdMqaEnabled":true}' # Enable PSNR, SSIM, and VMAF (with sampled VMAF every 2 keyframes) curl -X PUT http://localhost:5080/rest/scout/stream/LiveApp/myStream/mqa \ -H "Content-Type: application/json" \ -d '{"cmsdMqaEnabled":true,"cmsdVmafEnabled":true,"cmsdVmafSampleInterval":2}' # Disable all MQA for a stream curl -X PUT http://localhost:5080/rest/scout/stream/LiveApp/myStream/mqa \ -H "Content-Type: application/json" \ -d '{"cmsdMqaEnabled":false}' Response:: { "success": true, "streamId": "myStream", "cmsdMqaEnabled": true, "cmsdVmafEnabled": false, "cmsdVmafSampleInterval": 1, "vmafLibAvailable": false } The configuration is persisted in the AntMedia datastore (``Broadcast.metaData``) and takes effect the next time the stream starts. CMSD-MQA header --------------- When ``cmsdMqaEnabled=true`` for a stream, Scout injects a ``CMSD-MQA`` header on every ``.ts`` and ``.m4s`` segment response for that stream. The header is absent on manifest responses and on streams that have MQA disabled. Header format:: CMSD-MQA: psnr=42.31, ssim=0.9823, vmaf=87.54 Fields: .. list-table:: :widths: 15 20 65 :header-rows: 1 * - Field - Range - Description * - ``psnr`` - dB (decimal) - Peak Signal-to-Noise Ratio — full-reference, higher is better. Typical live stream values: 35–50 dB. Computed as mean PSNR over all frames in the segment. * - ``ssim`` - 0 – 1 (decimal, 4 dp) - Structural Similarity Index — full-reference, higher is better. Values above 0.95 are generally considered high quality. * - ``vmaf`` - 0 – 100 (decimal, 2 dp) - Video Multimethod Assessment Fusion — perceptual quality, higher is better. Only present when ``cmsdVmafEnabled=true`` and libvmaf is available. .. note:: The ``CMSD-MQA`` header name and field names (``psnr``, ``ssim``, ``vmaf``) are based on draft conventions publicly documented by Touchstream eVQA and Norsk. These names may be updated when the SVTA MQA specification is finalised and publishes normative field identifiers. Browser visibility ^^^^^^^^^^^^^^^^^^ Both ``CMSD-Static`` and ``CMSD-MQA`` are listed in ``Access-Control-Expose-Headers`` on every Scout-annotated response, so browser-based players can read them via ``response.headers.get('CMSD-MQA')``. .. note:: The ``CMSD-MQA`` header is populated from **on-device computed scores only**. Scores injected via the MQA REST ingest endpoint (see below) feed Prometheus and alerting but do not appear in the ``CMSD-MQA`` response header. MQA via REST ingest ------------------- In addition to computing MQA scores on-device, Scout can accept quality scores pushed by an external encoder or quality-assessment tool. This is useful when: - A GPU-accelerated or specialised analyser computes VMAF externally. - A separate probe or CDN monitoring system produces quality signals. - You want a common quality pipeline across AntMedia Server and Wowza Streaming Engine. The ingest endpoint is compatible with the Wowza Scout module's MQA receiver API, so the same score-pusher tool works with both server types. Enabling REST ingest ^^^^^^^^^^^^^^^^^^^^ Set ``restApiEnabled: true`` in ``scout.properties`` (requires a **Standard** or higher license). The endpoint starts automatically on ``restApiPort`` (default: 8090): .. code-block:: yaml restApiEnabled: true restApiPort: 8090 Push a score ^^^^^^^^^^^^ .. code-block:: bash POST /mqa/{streamName} Content-Type: application/json The ``{streamName}`` path segment is the stream ID (e.g. ``myStream``). Accepted body fields: .. list-table:: :widths: 25 25 50 :header-rows: 1 * - Field - Type - Description * - ``metric`` - String - One of: ``vmaf``, ``psnr``, ``ssim``, ``bitrate_kbps``. * - ``score`` - Number - The metric value. Examples:: # Push a VMAF score curl -X POST http://localhost:8090/mqa/myStream \ -H "Content-Type: application/json" \ -d '{"metric":"vmaf","score":87.3}' # Push a PSNR score curl -X POST http://localhost:8090/mqa/myStream \ -H "Content-Type: application/json" \ -d '{"metric":"psnr","score":43.1}' Responses: - ``200 OK`` — score recorded. - ``405 Method Not Allowed`` — only POST is accepted. - ``422 Unprocessable Entity`` — body is missing required fields or ``metric`` is not a recognised value. How injected scores are used ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injected scores are stored in memory and are immediately available to: - The **Prometheus endpoint** (see :ref:`Telemetry`) — injected scores override computed scores for the same stream and metric. - **Quality alerting** (see :ref:`Alerting`) — injected VMAF takes precedence over computed VMAF when evaluating thresholds. Injected scores are **not** included in the ``CMSD-MQA`` HTTP response header, which is populated exclusively from on-device computed scores. Compute overhead ---------------- .. list-table:: :widths: 20 80 :header-rows: 1 * - Metric - Overhead * - PSNR - Negligible. Computed in pure Java using the Y-channel pixels already present in memory; no additional decode pass is required beyond decoding the encoded segment (which Scout does internally, one decode per encoded packet). * - SSIM - Low. Computed alongside PSNR in the same decode pass using an 8×8 sliding-window algorithm on the Y channel. No separate processing step. * - VMAF - Moderate. Computed via the libvmaf AVFilter, sampled at keyframe boundaries (or every *N* keyframes when ``cmsdVmafSampleInterval > 1``). A full decode pass is required per sample frame. For a 30fps stream at 2s GOP length with the default sample interval of 1, this means approximately 1 VMAF call per 60 frames. To minimise overhead on high-bitrate renditions, consider: * Enabling MQA only on representative renditions (e.g. the 720p or 1080p track, not every adaptive variant). * Setting ``cmsdVmafSampleInterval`` to 2 or 3 to reduce VMAF sampling frequency. * Keeping ``cmsdVmafEnabled=false`` unless VMAF scores are specifically required. Checking for libvmaf -------------------- The ``vmafLibAvailable`` field in the GET response indicates whether the bundled FFmpeg was compiled with ``--enable-libvmaf``. The standard AntMedia Server distribution does **not** include libvmaf (it requires a GPL-licensed FFmpeg build). If ``vmafLibAvailable`` is ``false``, setting ``cmsdVmafEnabled=true`` will save the flag but produce a log warning at stream start and fall back to PSNR/SSIM only — it will never silently fail or crash. To enable VMAF: 1. Build or obtain an FFmpeg 7.x binary compiled with ``--enable-libvmaf``. 2. Replace the native FFmpeg libraries in AMS's library path with the new build. 3. Restart AMS — Scout will detect libvmaf at startup and log ``VMAF scoring is available``. You can verify the current state at any time: .. code-block:: bash curl http://localhost:5080/rest/scout/stream/LiveApp/anyStream/mqa | jq .vmafLibAvailable Implementation notes -------------------- **How PSNR and SSIM are computed:** For each MQA-enabled stream, Scout registers two internal listeners on the encoding pipeline. A frame listener buffers raw input frames (the encoder's input). A packet listener receives each encoded packet, decodes it using a lightweight software decoder, and compares the decoded output against the buffered reference frame using full-reference PSNR and SSIM algorithms on the Y (luma) channel. Scores are accumulated across all frames in a segment, and the mean is stored when the segment boundary is reached. **Memory implications:** The raw frame buffer is capped at 32 frames per stream. For a 1080p stream this is approximately 100 MB worst-case. For adaptive streaming ladders with multiple renditions, each rendition has its own independent buffer. Operators running MQA on many simultaneous high-bitrate streams should account for this when sizing server memory. **Per-stream isolation:** MQA listeners are registered and unregistered per-stream independently. Enabling MQA on stream A has no effect on the headers returned for stream B. **JNI bridge:** No new native JNI methods were added. All codec operations (decode, filter graph) use the existing bytedeco FFmpeg Java bindings (``org.bytedeco:ffmpeg:7.1.1-1.5.12``).