불안정한 네트워크에서 고품질 고해상도 영상 전송 #1

불안정한 네트워크

TCP/IP의 특성상 데이터를 전송할 때, 각 패킷이 목적지까지 도달하기까지 어떠한 경로를 이용하는지 알기는 쉽지 않습니다. 목적지가 정해진 경우에는 대개 ping 명령을 이용하여 ICMP(Internet Control Message Protocol)를 통해 간단한 테스트를 수행할 수는 있으나, 명칭에서 유추할 수 있듯, 데이터가 전달되는 프로토콜인 UDP 혹은 TCP와는 별개의 프로토콜을 사용하는 것이기 때문에 실제 데이터를 전송할때 특성은 ping에서 보여준 결과와 다르게 나타날 수 있습니다. 이러한 경우를 종단간 (end-to-end) 네트워크 상태에 대해서 예측이 불가능하다고 이야기하고 있으며 대게 다음과 같은 조건에서 불확실성은 더 높아지게 됩니다.

  1. 대역폭이 현저하게 큰 경우 (>= 1Gbps)
  2. 네트워크 경로가 긴 경우 (Hop의 수가 많아 패킷 도달 시간이 늘어나는 경우)
  3. 경로 내에 불특정 다수가 사용하는 무선 네트워크가 포함된 경우

불안정한 네트워크라는 추상적인 용어는 결국, 전달하고자 하는 데이터가 원하는 시간안에 도착하지 못함을 의미하게 됩니다. 네트워크 상에 가용한 대역폭이 증가하면서 예상치 못한 문제들이 발생하게 되는데, 이 문제는 이미 BDP(Bandwidth-Delay Product)로 정의되어 ACK 혹은 NACK 없이 네트워크 상에 떠돌게 되는 데이터의 양이 불확실성을 야기한다고 설명하고 있습니다. 본 글에서는 네트워크 상태를 판단할때 패킷 유실율을 고려하여 불확실성의 척도를 정의하고자 합니다. 불확실성의 척도는 위 세가지 조건을 각각 대역폭(B), 딜레이(D), 패킷 유실률(L)로 단순화하여 그 세기(Strength) 값으로 환산할 수 있습니다.

불확실성 척도 = B × D × w (1 + L) 

위와 같이 정의한 불확실성의 척도를 이용하여 두 네트워크 상태를 비교한다면 다음과 같이 됩니다.

  경로 A 경로 B
대역폭 (B) 1 Gbps 500Mbps
딜레이 (D) 20 ms 30 ms
패킷유실률 (L) 1% 2%
불확실성 척도 20,200 15,300

패킷 유실률을 얼마나 큰 요소로 간주할 것이냐에 따라 가중치인 w 값을 조절할 수 있으나, 문제를 단순화하기 위해서 1의 값을 가지는 것으로 하였습니다. 위 테이블에서 계산된 값은 경로 A경로 B보다 더 큰값을 가지게 되는데 이를 정의한 불확실성의 척도 개념에서 해석하자면, 경로 A가 좀 더 강한 세기의 불확실성을 가지고 있다고 말할 수 있습니다. 이 값은 통상적으로 대역폭이 높고 종단간 딜레이가 적다는 것이 데이터 전송에 유리할 것으로 여겨지는 것과는 상반되는 내용일 수 있습니다. 그러나 여기서 주의해야할 점은 불확실성의 척도는 단순히 두 네트워크의 상태를 비교하기 위한 개념일뿐이며, 상태의 좋고 나쁨을 의미하는 것은 아니라는 것입니다.

전송 관점에서 고품질 영상

영상 전송이 원할하게 잘 되고 있다는 판단을 할때는 시각적으로 보여지는 정보에 따라 주관적으로 판단하는 경우가 많습니다. 예를들어 FHD와 UHD, 혹은 10fps와 60fps 라는 단순 영상 속성 정보를 제공했을때, 당연하게도 해상도가 더 큰 UHD와 움직임이 부드럽게 보일것이라고 예상되는 60fps가 더 높은 품질의 영상일 것이라고 예상할 수 있습니다. 그러나 실시간 영상 송출하는 경우에는 영상 속성으로는 품질을 정의할 수 없는 한계가 있습니다.

$ ffmpeg -f avfoundation -i 0  -s 640x480 -r 25 -c:v libx264 -b:v 2M -maxrate 2M -bufsize 1M output.mp4
$ gst-launch-1.0 \
    avfvideosrc ! video/x-raw,format=NV12,width=640,height=480,framerate=25/1 ! videoconvert ! \
    x264enc tune=zerolatency bitrate=2048 ! \
    h264parse ! mp4mux fragment-duration=1000 ! filesink location=./output.mp4

위 두 명령은 각각 ffmpeg과 GStreamer를 이용하여, Apple Mac의 카메라 인터페이스를 이용하여 영상을 획득하고 x264 라이브러리를 이용하여 H.264로 영상 압축을 수행한 뒤 mp4 포맷으로 레코딩을 수행하는 것입니다. ffmpeg의 경우에는 약간 모호하지만, GStreamer 명령을 통해서 본다면 영상의 해상도, 프레임 수는 카메라에서 원본 영상을 얻는 것과 관련 있는 속성임을 쉽게 유추할 수 있습니다. 다만 주목할 만한 것은 인코딩 부분의 bitrate 입니다. 위 두 명령모두 2Mbps를 대역폭으로 설정하였는데, 이는 영상 인코딩 후 출력 대역폭을 의미합니다. 즉, 카메라 부분의 해상도, 프레임 수 등의 변경은 인코더의 입력 데이터 양과 관련이 있기 때문에, 인코더 출력 대역폭 설정을 변경하지 않고 해상도 등의 속성을 변경한다면 높은 해상도에서도 낮은 품질의 영상을 얻게 됩니다. 반면에 카메라의 속성을 변경하지 않고 인코더의 출력 대역폭을 증가시키면 보다 원본에 가까운 고품질의 영상을 획득 할 수 있습니다. 여러가지 복잡한 상황을 모두 제외하고 단순화한다면, 고품질 영상을 전송한다는 것은 더 많은 데이터를 전송해야한다는 것을 의미하게 됩니다.

Next: 불안정한 네트워크에서 고품질 고해상도 영상 전송 #2


The 2nd page of Hwangsaeul project

The 2nd page of Hwangsaeul project

It’s been almost a year and half since Jakub posted the installation document of Hwangsaeul (a.k.a H8L) project. The project members are still rush to develop the project for the various use cases. Although the H8L project is originally designed to support the massive deployment of video surveillance cameras which enable SRT transmission, we found that there is much more potential when we switch the domain to UAV(Unmanned Areial Viehicle) and UGV(Unmanned Ground Vehicle). Therefore, we had to change the overall shape of H8L to optimize for the new targets.

Deprecation of Messages Queue

The major architecture change for ​the second version of H8L is to remove the dependency on message queue by the deprecation of ​Chamge​. Although the module is good for exchanging complex messages among service components, it is an overkill architecture for real-time video streaming that focuses on video quality and ultra-low latency. In addition, there are lots of message queue based platform so we decided not to keep developing duplicated effort. Instead, we re-designed that each component provides D-Bus API and local settings. Since we removed this component, unfortunately, ABI compatibility with the previous version will not be guaranteed while this project is on-going.

Gaeul: Umbrella repository for streaming agents

Another change is done on the Gaeul. In the previous version, the module was in charge of edge streaming only. However, from the second version, it becomes a set of streaming agents; video source, relay, and even protocol conversion including transcoding. By analogy from the meaning of the word, it would be a very natural change for this module to handle the stream of data and video because Gaeul means babbling brook in Korean.

New Architecture

In new H8L, there are three major repositories; Gaeul, Hwangsae, and Gaeguli.

  • Hwangsae: SRT relay library
  • Gaeguli: Video stream source library
  • Gaeul: A set of video streaming services

Since Gaeul provides all of streaming agents, it will always require Hwangsae and Gaeguli depending on the feature it uses.

System Overview

Running H8L

Nightly builds are available as binary packages for Ubuntu 20.04.

$ sudo add-apt-repository ppa:hwangsaeul/nightly
$ sudo apt-get update

Note that the PPA provides important packages; libsrt and gst-plugins-bad. Since Ubuntu 20.04 provides slightly old version of those packages, some features like stream-id and SRT option strings are not supported. SRT(=1.4.2) and gst-plugins-bad with some patches that will be provided by newer version of GStreamer should be installed before using H8L features. Using PPA may be more convenient to test and have experience of H8L than building from scratch. Surely, some enhancement patches of SRT and GStreamer are already submitted to the upstream and most of them are landed onto master branch or ready for landing. If you’d like to check the patches, you can refer to our forked repositories; libsrt and gst-plugins-bad.

Source

The first agent of Gaeul is gaeul2-source-agent that is nomally simlilar to stream generator(or simply called as encoder).

$ sudo apt-get install gaeul2-source-agent

Settings

Before running the source agent, we need to set configurations to specify video parameters and SRT options. The settings are consist of two parts; one main configuration and multiple channel configurations.

Main Configuration

This configuration file is an entry point that provide capture device informations to the source agent. For the stream authentication, Stream ID is mandatory for H8L’s source agent and uid will be used for stream-id prefix for channels.

channel-configs option takes a list of absolute file paths that are channel configurations.

[org/hwangsaeul/Gaeul2/Source]
uid="device0"
channel-configs=["/etc/gaeul2/conf.d/ch0.ini", "/etc/gaeul2/conf.d/ch1.ini"]

Channel Configuration

This sub-configuration describes what type of SRT stream will be sent from which video capture device. In theory, the source agent can have an infinite number of channel configurations, but it will be limited by hardware performance. Normally, it shouldn’t exceed 2 channels encoding for 4K 30fps, or equivalent video encoding parameters.

The below is an example of a channel configuration.

[org/hwangsaeul/Gaeul2/Source/Channel]
name="channel0"
source="v4l2src"
device="/dev/video0"
bitrate=2000000
fps=30
codec="x264"
bitrate-control="CBR"
resolution="1920x1080"
target-uri="srt://ip.address:port/?mode=caller"
passphrase=""
pbkeylen="AES-128"
prefer-hw-decoding=false
record-location="/somewhare/recording-location"

With uid of a main configuration, name option will be used to compose a stream-id for this channel stream. if device0 is given for the uid and channel0 is for the name, this video channel will have device0_channel0 stream id.

Running

Now, it’s time to run the source agent. If the configurations are provided correctly, user can choose D-Bus mode; session, system and none.

If none is used, the agent will not try to acquire d-bus name. Regardless --dbus-type, it will send stream or trying to connect to the given target-uri of a channel configuration until SRT stream receiver is ready.

$ gaeul2-source-agent -c /etc/gaeul2/gaeul.ini --dbus-type=none

Debugging

If it is suspicious that the source agent sends a stream or not, the easiest way to check is to get log messages. Since all of H8L components follows GLib and GStreamer conventions, it shows log messages by setting two major environmental variables.

$ export G_MESSAGES_DEBUG=all
$ export GST_DEBUG=*:3
$ gaeul2-source-agent -c /etc/gaeul2/gaeul.ini --dbus-type=none

Then, now user can get log messages on their console.

Relay

The relay agent plays a role to distribute SRT stream from source to users. It provides stream authentication option to distinguish SRT session by stream-id. Surely, the authentication option can be disabled when it requires the compatiblity with the legacy SRT equipment or software.

Settings

Unlike the source agent, the relay agent has a main configuration only.

[org/hwangsaeul/Gaeul2/Relay]
uid="relay0"
sink-port=50010
source-port=60010
sink-latency=125
source-latency=125
external-ip="xxx.xxx.xxx.xxx"
authentication=true
  • uid: a unique id, it is used for identifying each relay service.
  • sink-port: a network port to be connected from a source agent.
  • source-port: a network port to be connected from a user or video stream consumer.
  • sink-latency, source-latency: SRT latency value for each connection.
  • external-ip: usually, it is used for VM in cloud service that has an external address.
  • authentication: an option to inteprete SRT streamid or not.

Running

If a configuration file is ready, it can be run with the below command. In a relay service, D-Bus API should be enabled to use a stream authentication feature. Here, it assumes that it uses session-wide D-Bus API.

$ gaeul2-relay-agent -c /etc/gaeul2/gaeul.ini --dbus-type=session

SRT Stream authentication

The relay is relatively simple because of SRT-nature; content agnostic. However, it plays very important role as a live stream distributor. In current implmentation, the relay agent supports only SRT’s live mode.

Stream Authentication in Relay

The relay provides a whitelist-based stream authentication mechanism. That means, the agent should know streamid before attempting to connect. Otherwise, the agent will reject stream connection.

To allow a connection from source agent, the stream-id of source agent must be registered. Here, it assumed that the source agent uses device0_channel0 as its stream-id.

$ busctl call \
    org.hwangsaeul.Gaeul2.Relay \
    /org/hwangsaeul/Gaeul2/Relay \
    org.hwangsaeul.Gaeul2.Relay \
    AddSinkToken "s" "device0_chanel0"

Then, if a user who has the unique id, admin0, wants to get a video stream of the source agent, device0_channel0, a source token should be registered too.

$ busctl call \
    org.hwangsaeul.Gaeul2.Relay \
    /org/hwangsaeul/Gaeul2/Relay \
    org.hwangsaeul.Gaeul2.Relay \
    AddSourceToken "ss" "admin0" "device0_channel0"

Playing SRT stream via Hwangsae

Currently, there are few video players that support SRT and its streamid.

  • VLC Nightly build (>= 4.0)
  • GStreamer (>= 1.18)
  • SRT Play Pro (iOS only)

If you are using GStreamer, you can test playing with the below command.

$ gst-launch-1.0 \
    srtsrc uri="srt://relay.ip:port?mode=caller" streamid="#\!::u=admin0,r=device0_channel0" ! \
    queue ! decodebin ! autovideosink

See also

H8L is not all about streaming video over SRT. It is actually designed to overcome unpredictable network in two major concerns. The first is how to stream high-quality video over high bandwidth capable network, and the second is to prevent video stuttering at a discernable level even if the quality is dropped. SRT dealt with the first issue very well by congestion control, but the second issue is complicated. Without media processing and network status prediction, it will be difficult to find solution. Here, we sugguest network adaptive streaming of H8L.


GstCaps small TIP: How to handle GstFeatures

문자열에서 GstCapsFeatures를 포함한 GstCaps 다루기

Nvidia 계열의 임베디드 보드에서 GStreamer Pipeline을 사용하는 경우 다음과 같은 예제를 볼수 있습니다.

 $ gst-launch-1.0 nvcamerasrc ! video/x-raw(memory:NVMM),width=1920,height=1080 ! nvvidconv ! autovidoesink

memory:NVMM은 Zero-copy 기능을 활성화하기 위해서 GstCaps에 부가적으로 제공하는 메타정보(GstCapsFeatures)이나, API를 사용하여 GstCaps를 생성할 때는 GstCapsFeatures를 직접 다룰 필요가 있습니다.

이와 관련하여 주로 접하게되는 Assertion Error는 다음과 같습니다.

GStreamer-CRITICAL **: gst_structure_new_empty: assertion 'gst_structure_validate_name (name)' failed
convFilterCaps could not be created. Exiting.

위 에러는 대게 gst_caps_new_simple() 함수를 이용하여 video/x-raw(memory:NVMM)을 처리할 것으로 기대한 경우에 볼 수 있습니다. 이는 gst_caps_from_string("video/x-raw(memory:NVMM)", NULL)이 동작한 것과 같은 방식으로 처리될 것으로 예상하고 사용한 경우에 볼 수 있는 에러이며, 메타정보를 정확하기 다루기 위해서는 다음의 코드처럼 GstCapsFeatures 객체를 별도로 생성하여 처리해야합니다.

/**
 * gcc -o caps_features `pkg-config --cflags --libs gstreamer-1.0` caps_features.c
 */

int
main (int argc, char **argv)
{
  GstCaps *a_caps, *b_caps;
  GstCapsFeatures *a_f, *b_f;

  gst_init (&argc, &argv);

  a_caps = gst_caps_from_string ("video/x-raw(memory:NVMM)");
  a_f = gst_caps_get_features (a_caps, 0);

  b_caps = gst_caps_new_simple ("video/x-raw", NULL);
  b_f = gst_caps_features_new ("memory:NVMM", NULL);

  gst_caps_set_features_simple (b_caps, b_f);

  g_print ("caps objects are %s\n", gst_caps_is_equal (a_caps,
          b_caps) ? "equal" : "*not* equal");

  gst_caps_unref (a_caps);
  gst_caps_unref (b_caps);

  return 0;
}