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.
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 on a separate cover page who did what work including programming the various software components, performance evaluation, and write-up. Designate one member as the contact person who will run turnin to submit the work. 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 fun (or not), and potential parallel speed-up if well executed. Regarding late days, for a group to use late days, every member of the group must have the same late days left.
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.
If you don't hear anything, check that audio
is not muted (under System -> Preferences -> Hardware -> Sound configuration tab)
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.
Playback of the two audio files serves as a reference for the quality of the stored audio without consideration of networking issues that we will tackle in the lab. 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 muaudio.c in the course directory. Similar to aplay, muaudio.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 to default value 313 msec (i.e., 313000). Test that it works by compiling muaudio.c with library -lasound and running it with the two .au audio files. Perceived audio quality should be similar to /usr/bin/aplay.
To port the playback component to your audio streaming client, copy the code pertaining to library functions mulawopen(), mulawclose(), mulawwrite() including header files and variable declarations to your client app. The code in muaudio.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(), muaudio.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 muaudio.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.
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, coolmusc, sends a TCP request packet to the server containing a 6-character password followed by the name of the file to be streamed which cannot exceed 10 characters. Both the password and filename must be from lower- or upper-case characters plus the punctuation symbol (i.e., '.'). If a request does not conform to the required format, the streaming server, coolmuss, sends a response that contains the single character 'E' then closes the socket returned by accept(). If a request is deemed valid, the parent process forks a child process that handles streaming of the requested audio file. It outputs a message to stdout indicating that a new streaming session has been accepted along with the audio file name and client's IPv4 address and port number. The parent goes back to waiting on the next client request after closing the socket returned by accept().
The child process creates a UDP socket and binds to an unused/ephemeral port number. After successfully binding the SOCK_DGRAM socket, the client uses the SOCK_STREAM socket inherited from its TCP to send a 1-byte response that contains ASCII character 'A'. It will use the UDP socket to stream audio packets to the client using sendto(), and receive feedback packets from the client using recvfrom(). The client, coolmusc, upon receiving a response on the SOCK_STREAM socket containing 'A' opens a UDP socket and binds to an unused port number starting at 55555. It sends the 2-byte port number followed by a 2-byte block size (see Section 1.4) over the TCP socket to the server. The server (i.e., child process of coolmuss) saves the port number received so that audio packets can be sent using sendto() to the client's IPv4 address and port number. The 2-byte block size will determine the fixed payload size of UDP packets carrying audio data.
The server coolmuss is invoked with command-line arguments
% coolmuss invlambda datalog.dat server-IP server-port
where invlambda of type float specifies the inverse
of the initial sending rate. That is, whereas lambda is in unit
of pps (packets per second), invlambda is 1/lambda so that it specifies
the time interval between successive packets in unit of second.
For example, invlambda = 0.000225 means that time interval between
successive packets is 225 microseconds.
datalog.dat is a log file where the time varying
sending rates (i.e., the inverse invlambda)
are recorded for diagnosis at the end of a streaming
session.
The last two arguments, server-IP and server-port, denote the
server's IPv4 address and TCP port number where coolmuss waits for client
requests to arrive.
Since the server can handle multiple clients, datalog.dat of the
first client should be appended with ".1", datalog.dat.1, the second client's
log file with "2", datalog.dat.2, etc. We will limit concurrent client
sessions to 4.
To reduce influence of system overhead when carrying out measurement operations, save the time varying invlambda values in an array in main memory until a streaming session has ended before writing the data to datalog.dat. Save the invlambda values, one per line, preceded by a timestamp from gettimeofday(). Subtract the first timestamp from successive timestamps so that time is normalized to start at 0 for the first logged invlambda value. Time stamps should be recorded in unit of millisecond with fractions truncated after 3 digits for microsecond resolution.
For our audio streaming app, we will adopt a dumb sender/smart receiver design
where the receiver (i.e., client) will command how the sender should adjust its
audio data sending rate.
The client, coolmusc, runs with command-line arguments
% coolmusc audiofile server-IP server-port invgamma param.dat cdatalog.dat
where audiofile is the name of a .au audiofile comprised of
lower- or upper-case characters plus the punctuation symbol (e.g., "pp.au").
server-IP and server-port specify the coordinate of the audio server.
invgamma is the inverse of the playback rate parameter gamma, by default,
set to 313 msec for our testing and benchmarking purposes.
param.dat is a text file that contains the following parameters:
blocksize which is an integer specifying the payload size of UDP packets
(its default value should be 4096 for testing and experiments),
buffersize which specifies the size (in bytes) of the audio buffer at the client
where audio packets received from the server will be kept before playback,
targetbuf (i.e., Q*)
which specifies the desired occupancy of client buffer (e.g.,
if buffersize is 10 KB and targetbuf 5 KB then the aim of streaming control
is to keep the client's buffer half full).
Three additional parameters invlambda, epsilon
and beta, all of type float, where invlambda is the
same initial packet spacing provided as command-line argument of coolmuss,
epsilon and beta are control parameters of congestion control method D.
The last command-line argument, cdatalog.dat, specifies the log file
where the time varying audio buffer occupancy is recorded.
A UDP feedback packet sent from client to server specifies up updated packetspacing, a 4-byte value of type float, which controls how fast the server transmits subsequent audio packets until the next feedback packet from the client is received. Upon receiving a feedback packet, the server updates invlambda accordingly. The clients computes the updated packetspacing value using control law D: first, subtract the desired/target buffer occupancy Q* from current audio buffer occupancy Q(t) (t means now), then multiply the different by parameter epsilon; second, subtract the previous invlambda from invgamma which is then multiplified by control parameter beta. Since we are working with packetspacing as the control variable instead of sending rate, the direction of update in control law D is reversed. For example, if Q(t) falls below Q* (there is too little buffered future audio) then Q(t) - Q* is negative which implies that the updated invlambda value will be decreased thus increasing the rate at which future audio packets are sent aimed at increasing buffer occupancy. The second term, as discussed in class, is more subtle where if invgamma - invlambda is negative (sending rate is too slow) then the updated invlambda is decreased thus increasing sending rate so that Q(t) approaches Q* from below. On the other hand, if invgamma - invlambda > 0 then sending rate lambda is already faster than playback rate gamma which, in conjunction with Q(t) being below Q*, indicates that Q(t) previously exceeded Q*. Thus the second term works in the opposite direction of the first term which acts as a dampening factor that contributes to convergence of the system to optimum buffer size Q(t) = Q* and invlambda = invgamma.
As in the server, the client logs current buffer occupancy Q(t) along with a normalized timestamp in main memory which is output to log file cdatalog.dat when streaming has terminated. Since all amber machines in our use NFS to mount user home directories, write the log file in /tmp which will be part of the local file system of a specific amber machine. Note that files in /tmp may be deleted by the system during cleanup operations, hence use the mv command to relocate its content to a safe place in your home directory. After the last data packet has been transmitted, coolmuss (i.e., child process) transmits character '$' using the TCP socket which notifies the client that streaming has ended.
Default block size is fixed to 4096 bytes to be compatible with the internal parameters that mulawopen() and mulawwrite() are configured with. If blocksize is smaller than 4096, it can introduce complications that are best avoided in lab5. All audio data related parameters including targetbuf and blocksize should be in multiples of 4096.
Describe the software architecture of your client/server implementation and its rationale (over alternative implementations) in lab5.pdf. In lab2 and lab3 our software design focused on handling asynchronous events using signal handlers. In lab4 the VPN server (i.e., child process) implemented a blocking design using select(). It is up to you to determine which design, including hybrid, may be suited for lab5.
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 initial lambda values and other control parameters. In the client code, define a macro CONTROLLAW that is set to 0 or 1. If 0, method D is carried out by the client when updating invlambda that is sent to the server as feedback. If 1, method B is performed. Note that control law B has different control parameters. When running B, interpret epsilon in param.dat as additive constant a in the control law, and beta as multiplicative factor delta. Plot the the time series measurement logs using tools such as 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.
Add an additional control law, method E, selected by setting the macro CONTROLLAW to 2 in the client. When designing E, exploit the property of our multimedia streaming set-up where the playback speed gamma is fixed (its inverse 313 msec). A two-phase control can be envisioned where under favorable network conditions with low losses -- as is generally the case in our lab environment -- the first phase prefetches Q* audio data before playback commences. The second phase which immediately follows the first phase has the server transmitting to the client audio packets at a pace that matches the known fixed value gamma (in our version, mapped as time interval between successive packets). Thus feedback is not actively utilized, the system operating in a manner called open-loop control where future actions are pre-determined. In contrast to the more general feedback or closed-loop control that control method D embodies. When implementing open-loop control in this manner, it is important to set the initial invlambda such that, viewing the client's audio buffer as a producer/consumer queue, the actual rate of writing into the buffer matches the actual rate of reading (i.e., playback) from the buffer. When inaccuracies are present, the longer an open-loop control system runs the greater the chance that behavior of the system will deviate from its intended target. Describe the specifics of your design and implementation in lab5.pdf. Evaluate performance by plotting the client/server performance graphs and comparing them to the results of Problem 1. Submit the modified code in v2/. If Problem 1 is tackled as a group effort, the bonus problem must be solved as a group effort as well.
Electronic turn-in instructions:
i) For problems that require answering/explaining questions, submit a write-up as a pdf file called lab5.pdf. Place lab5.pdf in your directory lab5/. You can use your favorite editor subject to that it is able to export pdf files which several freeware editors do. Files submitted in any other format will not be graded.
ii) We will use turnin to manage lab assignment submissions. Please check that relevant source code including Makefile are included in the relevant subdirectories of lab5. In the parent directory of lab5, run the command
turnin -c cs536 -p lab5 lab5
You can check/list the submitted files using
turnin -c cs536 -p lab5 -v