CS 536 Spring 2024

Lab 5: Congestion Control for Audio Streaming [250 pts]

Due: 4/10/2024 (Wed), 11:59 PM

Objective

The aim of this lab is to implement feedback congestion control for pseudo real-time multimedia streaming. To not get sidetracked by video/audio coding standards and related issues, we will use a representation close to raw audio streaming which allows us to focus on the networking components.


Reading

Read chapters 5 and 6 from Peterson & Davie.


Problem 1 (250 pts)

1.1 Audio playback on lab machines

Prior to CS Linux lab machines transitioning to Ubuntu Linux, our Linux distros supported /dev/audio that provided a simple interface to play back audio. For example, to play back an audio file in .au format calling open() on /dev/audio and writing the content of the .au file sufficed. On command line, % cat somefile.au > /dev/audio, sufficed for playback on the default audio device of a lab machine running Linux or UNIX. With cessation of /dev/audio support in Ubuntu on our lab machines, we will use a slightly more complicated interface to play back the content of an .au audio file on the default ALSA audio device of the Linux PCs. To get familiar with performing audio playback, copy two sample audio files pp.au (short piece) and kj.au (longer piece) from the course directory, and run

% /usr/bin/aplay pp.au

with an earphone plugged into an amber machine (audio jack is located on the left edge of the display). If you don't hear anything, check that audio is not muted (under System -> Preferences -> hardware -> Sound configuration tab in Ubuntu MATE). Do the same for kj.au which is a bit longer. If you don't have access to an earphone, please contact the TAs or me.

After playback is confirmed using aplay, you will need to code playback within the client code of the pseudo real-time audio streaming app. To do so, inspect the code testaudio.c in the course directory. Similar to aplay, testaudio.c takes an audio filename as command-line argument and plays the content of the file on the ALSA audio device. A second command-line argument specifies the time interval (in microseconds) between successive writes to the audio device. Set this value to 313 msec (i.e., 313000). Test that it works by compiling testaudio.c with library -lasound and running it with the two .au audio files.

To port the playback component to your audio streaming client, copy the code pertaining to mulawopen(), mulawclose(), mulawwrite() including header files and variable declarations to your client app. The code in testaudio.c reads the content of an .au file in unit of 4096 bytes (bufsiz) into main memory (buf), then writes the 4096 bytes contained in buf to the audio device by calling mulawwrite(). Between successive calls to mulawwrite(), testaudio.c calls usleep() to sleep for a fixed period specified by the argument slptime. The parameter slptime (in unit of microseconds) is provided as the second command-line argument of testaudio.c. Relating slptime to the playback rate gamma in the pseudo real-time streaming model, the larger slptime the smaller gamma since the latter is a rate. The recommended value of slptime to use in your client is 313000 microseconds. Use nanosleep() instead of usleep() to sleep between successive read/write of 4096 bytes to the audio device in the client.

1.2 Pseudo real-time audio streaming

Implement and evaluate an UDP-based pseudo real-time audio streaming application following the congestion control framework for multimedia streaming discussed in class. The client, audiostreamc, sends an UDP request packet to the server containing the name of the file to be streamed. We will limit filenames to 20 characters. We will not allow spaces in the filename. If a filename longer does not conform to the required format, the streaming server ignores the request. The client transmits two space characters after the filename as delimiter followed by two bytes representing the blocksize. The server, audiostreams, parses a client request, and if a request is deemed valid, the parent process forks a child process that handles streaming of the requested audio file.

The child process creates a new UDP socket with the same IP address but a different unused/ephemeral port number. It uses the new socket to perform audio streaming to the client using sendto() which entails calling recvfrom() to receive client feedback. The parent process, a concurrent server, goes back to waiting for a new client request.

1.3 Server structure

The server is invoked with command-line arguments

% audiostreams lambda epsilon gamma logfileS server-IP server-port

where lambda is the initial sending rate in unit of pps (packet per second). Real numbers epsilon and gamma specify the control parameters of congestion control method D used by the server. logfileS specifies the name of a log file where the time varying sending rates are recorded for diagnosis at the end of a streaming session. The last two arguments, server-IP and server-port, note the server's IPv4 address and port number, respectively. Since the server can handle multiple clients, logfileS of the first client should be appended with "-1", logfilesS-1, the second client's log file with "-2", logfilesS-2, ... to distinguish the log files.

To reduce influence of system overhead, save the time varying lambda values in an array in main memory during the streaming session. Only at the end of the streaming session before a child process terminates is the array written to its log file. Along with each lambda value, a timestamp is saved. Use gettimeofday() to get timestamps but subtract the first timestamp from successive timestamps so that time is normalized to start at 0 for the first logged lambda value. Save time stamp in unit of millisecond with fractions truncated after 3 digits to indicate microsecond resolution.

Command-line argument lambda specifies a rate in unit of pps, however, for actual packet transmission and control of data rate it is simpler to use 1/lambda, i.e., time interval between successive packet transmission as control variable. We will call 1/lambda, packetinterval, maintained in unit of millisecond. Use nanosleep() with packetinterval as input to affect how fast the server sends audio data to the client. When logging lambda, record packetinterval which will be easier to interpret when diagnosing performance at the end of a streaming session.

For our audio streaming app, we will adopt a smart sender/dumb receiver design. A feedback packet sent from client to server specifies how full the client's buffer is using two bytes interpreted as unsigned short. This value, unsigned short bufferstate, ranges from 0 to 20 where 10 indicates that the client's buffer occupacy is just right. A value closer to 0 signals that the client's buffer is closer to depletion. A value closer to 20 indicates that the buffer is close to full. In our smart sender/dumb receiver design, the server adjusts its sending rate based on feedback received. We will use congestion control method D where the constant 10 is treated as the target buffer size Q*. From the perspective of control method C, if bufferstate equals 10 then no adjustments are made to the sending rate lambda. Since method D contains an additional component involving gamma (313 msec in our setup), even if bufferstate specified in the feedback packet is 10 an adjustment to lambda may occur due to the dampening factor. Control parameters epsilon and gamma are specified as command-line arguments of audiostreams.

The value of packetinterval is logged whenever a data packet is transmitted to the client. The command-line lambda value is only used to calculate the initial packetinterval 1/lambda. When feedback packets arrive sending rate is updated using control method D. The size of UDP's payload when sending a data packet to the client is determined by the blocksize communicated by the client during session set-up. After the last data packet has been transmitted, audiostreams transmits 5 UDP packets containing no payload signifying that the session has ended. The child process writes the logged values to logfileS and terminates.

1.4 Client structure

The client, audiostreamc, runs with command-line arguments

% audiostreamc audiofile blocksize buffersize targetbuf server-IP server-port logfileC

where audiofile is the name of the audio file, blocksize the payload size (by default 4096 bytes), buffersize the size of the client's buffer where audio data received from the server are held before playback on the ALSA audio device, targetbuf is the target buffer size Q*. The unit of buffersize is in multiples of 4096 bytes which is the fixed size that mulawopen() and mulawwrite() are configured with. If blocksize is smaller than 4096, it may happen that the client's FIFO buffer is not empty and contains less than 4096 bytes. In such cases, the client process does not read the bytes in its buffer to send to the audio device by calling mulawwrite() but treats it as if empty. Buffer occupancy must be greater or equal to 4096 bytes for playback to occur. The parameter targetbuf, also in multiples of 4096 bytes. The arguments server-IP and server-port specify the server's IPv4 address and port number, logfileC is the name of the logfile where the client records its buffer occupancy changes.

The client's FIFO buffer is a classical producer/consumer queue where the producer is the code that calls recvfrom() to receive the next data packet from the server and copies its payload to the FIFO buffer. The consumer is the code of the client that every 313 msec --- we will use this fixed value which yields acceptable audio quality if the buffer is never depleted --- reads 4096 bytes from the buffer and calls mulawwrite() to send the audio content to the audio device. Please utilize standard concurrency control primitives such as semaphores to prevent corruption of the shared FIFO data structure. For example, in one implementation recvfrom() may be implemented synchronously that blocks on arrival of data packets from the server, whereas reading from the FIFO buffer is implemented asynchronously by coding a signal handler that is invoked every 313 msec. Whenever a read or write operation is performed on the FIFO buffer its occupancy (i.e., Q(t)) is logged into an array in main memory. Buffer size is recorded along with timestamps obtained from gettimeofday(), but normalized to start from 0 in unit of milliseconds as in the server. Just before the server terminates, the logged values are written to logfileC appended by the client's PID. For example, if PID is 22222 the filename is set to logfileC-22222.

Whenever a read/write operation is performed on the FIFO buffer, the client calculates q = (Q(t) - targetbuf) / (buffersize - targetbuf) if Q(t) > targetbuf. Then it transmits a feedback packet to the server containing the value q * 10 + 10 rounded to an integer. For example, if buffersize is 1000, targetbuf 500, and Q(t) = 600 then q = (600 - 500) / (1000 - 500) = 0.2. The feedback contains 0.2 * 10 + 10 = 12. If Q(t) < targetbuf the client calculates q = (targetbuf - Q(t)) / targetbuf and sends feedback packet containing 10 - (q * 10). For example, if for the above example Q(t) = 400 then q = (500 - 400) / 500 = 0.2. Feedback contains 10 - 0.2 * 10 = 8. If Q(t) equals targetbuf the client sends feedback value 10.

When the client starts up, it sends a streaming request packet containing a filename (up to 20 bytes) followed by two space characters and 2-byte blocksize the filename. The 2-byte block size will encode a value up to 4096, with smaller sizes such as 1472 (bytes) fitting as payload of an Ethernet frame (1472 = 1500 - 20 - 8). It sets a 2-second timer to terminate if a data packet from the server does not arrive.

1.5 Performance evaluation

To evaluate how well the pseudo real-time streaming app performs, plot the client and server log files which will show if the system is converging to the target buffer occupancy targetbuf, and, if so, how fast. Run the server and client(s) on different lab machines Configure buffersize in the mid 60 KB (multiple of 4096 bytes) range, and targetbuf half its size as a reasonable starting point. Experiment with different blocksize, initial lambda values, and control parameters. In the server code, define a macro CONTROLLAW that is set to 0 or 1. If 0, method D is carried out by the server. If 1, method C is performed. Plot the the time series measurement logs using gnuplot, MatLab, or Mathematica which will help diagnose how your streaming control is performing. gnuplot is very easy to use and produces professional quality graphical output in various formats for inclusion in documents. Discuss your findings in lab5.pdf. Submit your work in v1/ following the convention of previous labs along with README and Makefile.

Note: This problem may be tackled as a group effort involving up to 3 people. If going the group effort route, please specify in lab5.pdf who did what work including programming the various client/server components, performance evaluation, and write-up. Please designate one member as the contact person who will email the GTAs by 4/2/2024 who the group members are. You may split a group after the date but not form new groups or add new members. Whether you implement lab5 as an individual effort or make it a group effort is up to you. Keep in mind the trade-offs: group effort incurs coordination overhead which can slow down execution. Benefits include collaborative problem solving and some parallel speed-up if efficiently executed. Regarding late days, for a group to use k (= 1, 2, 3) late days, every member of the group must have k late days left.

Bonus problem (50 pts)

Add an additional control law, method H, selected by setting the macro CONTROLLAW to 5 in the server. Describe the idea and pseudo-code behind your method in lab5.pdf. For example, you may exploit the fact that payback rate at the client is constant which may be communicated to the server. The additional information can help improve control actions. You may also consider changing the control setup to a traditional engineering framework (i.e., PID control) where the sending rate is not incremented/decremented but set to specific sending rates. Evaluate performance by comparing to the results of Problem 1. Submit the extended code in v2/. If Problem 1 is tackled as a group effort, the bonus problem must be solved as a group effort as well.


Turn-in Instructions

Electronic turn-in instructions:

We will use turnin to manage lab assignment submissions. Place lab5.pdf under lab5/. Go to the parent directory of the directory lab5/ and type the command

turnin -v -c cs536 -p lab5 lab5


Back to the CS 536 web page