Video Serving and HTTP Range Requests

I generate a lot of video output from running simulations and often find myself trying to figure out a way to quickly share them with colleagues who are typically in the same building. Many times these are temporary results and which do not warrant even copying to the temporary shared drive.

Typically, when I want to share documents, I use the venerable Python module SimpleHTTPServer which is installed by default on virtually every modern operating system. However, the user experience of playing a video served by SimpleHTTPServer leaves a lot to be desired. Seeking, one of the most important interactions, especially for longer videos is not possible when using SimpleHTTPServer.

When I first started learning Go, I wanted to see how difficult it would be to develop something like SimpleHTTPServer. Turns out writing a static FileServer is a trivial task in Go requiring just a few lines of code as follows:

import (
    "log"
    "net/http"
)

func main() {
    log.Printf("Serving %s on http://localhost:8081", http.Dir("."))
	log.Fatal(http.ListenAndServe(":8081", http.FileServer(http.Dir("."))))
}

However, when I tried to play a video using this humble server, the experience was completely different. Seeking simply worked. I knew I wanted to get this responsive video seeking behavior with SimpleHTTPServer. Because …, why not?

Go standard library source code is very readable. For example, all the code needed to understand http.FileServer is organized into the file src/net/http/fs.go. Reading through fs.go, I was found to two other resources, RFC 2616 - HTTP/1.1 and RFC 7233 - HTTP/1.1: Range Requests that helped me understand that the magic behind Go’s FileServer is Byte Serving. After understanding HTTP range requests, I next looked at the source of SimpleHTTPServer.py. I found that all we need to do is overwrite a couple of functions and we will have a proof-of-concept for handling range requests.

For the experiment, I use sample videos from the following locations:

Before we move on to the code, here are two videos showing the user experience with SimpleHTTPServer and FileServer. Click on the images to see a larger version.

Video served using Python's SimpleHTTPServer
Python's SimpleHTTPServer
Video served using Go's http.FileServer
Go's http.FileServer

The final code is so small that it did not warrant a repository of its own. You can find it in on GitHubGist. A copy is also available at the end of this post.

Reading through SimpleHTTPServer.py, the server is actually created through a call from __main__ to test(). We will use this function albeit replacing the HandlerClass parameter.

Since the SimpleHTTPRequestHandler class contains a lot of other functionality like cleaning and translating paths, guessing file types, etc., we will use it as the base class for our RangeHTTPRequestHandler.

import os
import SimpleHTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler

class RangeHTTPRequestHandler(SimpleHTTPRequestHandler):
    """RangeHTTPRequestHandler is a SimpleHTTPRequestHandler with HTTP 'Range'
    support
    """

    def send_head(self):
        """Common code for GET and HEAD commands.
        Return value is either a file object or None
        """
        # This is one of the functions we need to implement
        pass

    def copyfile(self, infile, outfile):
        """Copies data between two file objects
        If the current request is a 'Range' request then only the requested
        bytes are copied.
        Otherwise, the entire file is copied using
        SimpleHTTPRequestHandler.copyfile
        """
        if not 'Range' in self.headers:
            SimpleHTTPRequestHandler.copyfile(self, infile, outfile)
        else:
            # We need to provide our own way to copy the requested range of
            # bytes from infile to outfile
            pass


if __name__ == '__main__':
    SimpleHTTPServer.test(HandlerClass=RangeHTTPRequestHandler)

That’s it, these are the two functions we need to fill out to create a new subclass of SimpleHTTPRequestHandler to handle HTTP/1.1 Range header.

The logic of the send_head function is as follows:

  • If the path is a directory, let SimpleHTTPRequestHandler handle the request
  • If the path does not exist, send HTTP 404
  • If ‘Range’ is present in self.headers then parse the start and end bytes
    • ‘Range’ header looks like bytes=<start-byte>-<end-byte>
    • Both start-byte and end-byte are optional
    • Missing start-byte means the request is for the last ‘end-byte’ bytes
    • Missing end-byte means the request is from start-byte to the end of file
  • If ‘Range’ request, then send response HTTP 206 else HTTP 200
  • Add headers ‘Accept-Ranges’ and ‘Content-Length’ to help browsers send out the correct ‘Range’ requests

Implementation of send_head:

def send_head(self):
    """Common code for GET and HEAD commands.
    Return value is either a file object or None
    """

    path = self.translate_path(self.path)
    ctype = self.guess_type(path)

    # Handling file location
    ## If directory, let SimpleHTTPRequestHandler handle the request
    if os.path.isdir(path):
        return SimpleHTTPRequestHandler.send_head(self)

    ## Handle file not found
    if not os.path.exists(path):
        return self.send_error(404, self.responses.get(404)[0])

    ## Handle file request
    f = open(path, 'rb')
    fs = os.fstat(f.fileno())
    size = fs[6]

    # Parse range header
    # Range headers look like 'bytes=500-1000'
    start, end = 0, size-1
    if 'Range' in self.headers:
        start, end = self.headers.get('Range').strip().strip('bytes=').split('-')
    if start == "":
        ## If no start, then the request is for last N bytes
        ## e.g. bytes=-500
        try:
            end = int(end)
        except ValueError as e:
            self.send_error(400, 'invalid range')
        start = size-end
    else:
        try:
            start = int(start)
        except ValueError as e:
            self.send_error(400, 'invalid range')
        if start >= size:
            # If requested start is greater than filesize
            self.send_error(416, self.responses.get(416)[0])
        if end == "":
            ## If only start is provided then serve till end
            end = size-1
        else:
            try:
                end = int(end)
            except ValueError as e:
                self.send_error(400, 'invalid range')

    ## Correct the values of start and end
    start = max(start, 0)
    end = min(end, size-1)
    self.range = (start, end)
    ## Setup headers and response
    l = end-start+1
    if 'Range' in self.headers:
        self.send_response(206)
    else:
        self.send_response(200)
    self.send_header('Content-type', ctype)
    self.send_header('Accept-Ranges', 'bytes')
    self.send_header('Content-Range',
                     'bytes %s-%s/%s' % (start, end, size))
    self.send_header('Content-Length', str(l))
    self.send_header('Last-Modified', self.date_time_string(fs.st_mtime))
    self.end_headers()

    return f

The second function copyfile is much simpler to implement. We read chunks of data from the input file object and write it to the output file which will be automatically flushed to the client side by the server.

An implementation of copyfile is as follows:

def copyfile(self, infile, outfile):
    """Copies data between two file objects
    If the current request is a 'Range' request then only the requested
    bytes are copied.
    Otherwise, the entire file is copied using SimpleHTTPServer.copyfile
    """
    if not 'Range' in self.headers:
        SimpleHTTPRequestHandler.copyfile(self, infile, outfile)
        return

    start, end = range
    infile.seek(start)
    bufsize=64*1024 ## 64KB
    while True:
        buf = infile.read(bufsize)
        if not buf:
            break
        outfile.write(buf)
Video served using RangeHTTPServer
RangeHTTPServer

Important Note: It is very important to note that this the code presented in this post is a proof-of-concept and should not be used for production or otherwise critical use cases. The code presented doesn’t even implement the complete requirements of RFC 7233. For example, the presented server can only hand one range. A lot of input validation and error handling, e.g. checking if the range request is properly formatted is not implemented for the sake of brevity.

For the sake of posterity, the following links are copies of relevant files:

Go Game of Life

Going through the Awesome Go list, I came across a new library under the Game Development section named Pixel. Since I have been looking for a simple game development library in Go for visualization of cellular automata and evolutionary algorithms simulations.

After spending a few minutes with the Pixel’s documentation, I was impressed with the library and decided to develop a simple interactive demo for exploring Conway’s Game of Life.

Game of Life is a 2D cellular automata and a zero-player game whose evolution only depends on the initial configuration. The rules of the game as per its Wikipedia page are as follows:

The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, alive or dead, or “populated” or “unpopulated”. Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur:

  1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by overpopulation.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed—births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called a tick (in other words, each generation is a pure function of the preceding one). The rules continue to be applied repeatedly to create further generations.

Even with these simple and local rules, the Game of Life is known to produce some very interesting static, oscillating and repeating patterns.

I wanted to see how easy it would be to develop the following features:

  1. Draw individual pixels and primitives (lines, circles, polygons, etc.)
  2. Keyboard interaction (press, hold, release)
  3. Mouse interaction (press, hold, release, double-click)

With a few hours of copy-pasting from the Pixel examples and some own coding, I was able to create a demo with the following features:

  1. Store the state of the world in a 2D array
  2. Scale the world to 4x to make the cells visibly distinguishable
  3. Draw the cells as filled rectangles (also tried circles)
  4. Left-click to set a cell alive
  5. Alt+Left-click to set a cell to dead
  6. Hold the click (and Alt variant) to continuous set cell states
  7. Ctrl+Left-click to reinitialize a fixed 11x11 area around the mouse position
  8. Press c and r to clear and reinitialize the entire world, respectively
  9. Press Space to toggle pausing the simulation

Except for double-click, which I could not figure out, all other features were relatively easy to figure out as well as implement.

The resulting code is available at https://github.com/shivakar/go-game-of-life. Here’s a screencast of one run of the simulation.

Conway's Game of Life demo

Additional criteria that I am interested in include:

  1. Ease of installation of the library

    As with most Go packages installation was easy:

    go get -u github.com/faiface/pixel
    
  2. Dependencies of the library

    The number and quality of dependencies of a library are something that I always look at when selecting a library. I find that too many dependencies or low-quality dependencies tend to create usability issues down the line.

    Here’s the dependency graph for github.com/faiface/pixelgl generated using goviz Pixel library
dependencies

    As evident from the dependency graph, the only dependencies outside the standard library are the excellent go-gl (OpenGL with Golang) and golang.org/x packages.

  3. Performance

    I ran these experiments on my MacBook Air (mid-2012) running macOS Sierra (10.12) and Go 1.8.1.

    With cells being drawn as filled rectangles and VSync set to false, I saw frame rates between 250-500 fps depending on how many cells in the world were alive. With VSync set to true, the frame rate was synchronized to 60fps monitor refresh rate.

    However when the cells were drawn as filled circles, the frame rate dropped to 5-50 fps.

    Profiling with pprof I noticed that the top CPU consuming functions went from

    flat%  cum%
    37.76% 37.76%  main.(*GameOfLife).NeighborsAlive
     6.98% 17.73%  main.(*GameOfLife).Draw
     4.37% 42.13%  main.(*GameOfLife).Update
     2.23%  2.92%  github.com/faiface/pixel/imdraw.(*IMDraw).applyMatrixAndMask
     1.23%  6.60%  github.com/faiface/pixel/imdraw.(*IMDraw).fillRectangle
    

    to

    flat%  cum%
    20.92% 24.35%  github.com/faiface/pixel/imdraw.(*IMDraw).applyMatrixAndMask
    15.07% 15.07%  github.com/faiface/pixel/pixelgl.(*GLTriangles).updateData
    10.82% 10.82%  math.Sincos
     3.72%  3.72%  main.(*GameOfLife).NeighborsAlive
     3.70% 50.00%  github.com/faiface/pixel/imdraw.(*IMDraw).fillEllipseArc
     2.15% 11.09%  github.com/faiface/pixel.(*TrianglesData).SetLen
    

    SVGs of the full CPU profile graphs are available for drawing cells as rectangles and circles.

    I haven’t looked (and most probably won’t) into the underlying causes but it appears that the library is just slow at drawing circles than rectangles. While not a deal breaker, I will have to develop a few more sample programs to get a better understanding of the strengths and weaknesses of the library.

All things considered, I am excited to see new packages like Pixel address the lack of good and easy-to-use GUI, visualization and game development libraries for Go. Although this does not end my search for a game development library in Go, I will certainly keep track of Pixel development.

Why do we read?

I was a teenager when I was first learned of the classification of knowledge into “know that”, “know how” and “knowledge by acquaintance”. It was using that knowledge that I could, for the first time, convincingly answer to myself that “…we can never know if the ‘red’ color I see is the same as another person since that knowledge is gained by a previous acquaintance with ‘red’. We both agree on what ‘red’ is, even though we may never know ‘how’ the other perceives that ‘red’”. Subsequently, I have always been fascinated by Epistemology, the study of nature and theory of knowledge and knowledge acquisition.

Going through one of my old notebooks, I found some thoughts on the question, “Why do we read?”. I noted that while reading is arguably one of the dominant forms of knowledge acquisition since the invention of the printing press, knowledge acquisition cannot be the only reason why we read. Apparently, the goal of the exercise was to list 10 reasons for the question. The way I went about the task was by taking a list of reading sources that I used frequently and identifying the reason I personally was drawn to the source. I decided to revisit the question and here is the list of reasons I could come up with:

  • To be informed - We read to keep ourselves informed about the revolutionary things happening around us, from what’s going on in the world in general to the latest developments in the fields of our choice.

    Example sources: Newspapers, press releases

  • To be part of our world - We read to be knowledgeable enough to be able to engage with our peers and the world at large on issues of interest.

    Example sources: Newspapers

  • Out of curiosity - Sometimes, we simply read to satisfy the insatiable curiosity that defines human nature.

    Example sources: All readable sources

  • To discover fresh ideas and different opinions - We read to discover ideas and opinions that are new, innovative or just different from our own. Sometimes, when we don’t know enough to have an opinion of our own, reading someone else’s opinion provides the necessary foundation.

    Example sources: Opinions, blogs, scientific literature

  • To learn something new - We read to expand our personal capabilities by acquiring new skills.

    Example sources: Textbooks, course materials

  • For mental stimulation - We read cutting edge literature to stimulate our own mental processes.

    Example sources: Research papers, science fiction

  • To discover the truth - We read to distinguish truth from rumor and speculation and to understand the context around a particular piece of information.

    Example sources: Investigative reports, historical accounts, peer reviews

  • To explore the world - We read to learn about the world, its cultures, and history without ever having to leave our physical sanctuary. Reading provides us a way to overcome any physical or economic challenges and transport ourselves to anywhere in the world.

    Example sources: Travel and exploration reports, Newspapers

  • To travel back in time - We read to learn about people, places, and events that existed a long time ago.

    Example sources: Biographies, historical accounts

  • To escape reality - Sometimes, we read to explore alternate realities and universes which are remotely plausible at best.

    Example sources: Science fiction

  • To learn at our own pace - Reading is one effort where we are in complete control over the rate of assimilation.

    Example sources: All readable sources

  • When there is no other choice - Some knowledge is currently only available as the written word.

    Example sources: Books on non-mainstream research topics

  • Entertainment - Sometimes, we read for our own entertainment, because we are bored.

    Example sources: Comics, poetry, novels

My Personal Blog

I’ve wanted to maintain a regular blog for over a decade. A few years ago, after numerous failed attempts, I simply gave up on the idea as something I would eventually get to, but just not right then. I believe that it is an opportune time in my life to make another attempt at blogging.

Starting 2017, Bharath & I, quit our full-time jobs and began a year-long exploration into several areas of common interest with the goal of identifying a commercial idea that we would be interested in spending the next 5-10 years of our lives. As part of building our company, we have decided to build our personal brands as well. At this time, I cannot think of a better medium to reach a large audience than a blog. With the ability to dedicate necessary and sufficient time, I am encouraged to start this blog as a new habit that will greatly help me document, as well as, better communicate new research and development activities.

Over the last decade and a half, I’ve had the privilege of working on a number of multi-disciplinary projects which have taught me a few interesting lessons. One of these is the belief that a successful team has more breadth of knowledge about the problem at hand than the depth of knowledge on individual tasks to be solved. Another strong observation is that cross-pollination of ideas across disciplines more often than not produce such wonderful results that are not only nearly impossible to predict but even imagine. I have always strived to bring a breath of knowledge to every team I was a part of and try to explore and harness every opportunity to gain a new perspective on the problem. I intend to use this blog as not only an outlet to showcase the work that I am doing, but also to meet prospective teammates and collaborators. I would consider this blog a successful endeavor when it leads to productive collaborations on interesting and impactful projects.

The blog will primarily be technical in nature. Many of the personal projects I worked have been too small (and many times unoriginal) to merit a published article and sufficiently technical enough that the resulting product (mostly source code) is not enough for someone to dive into and understand the intent of the project or the decision-making process behind its design and development. These projects would find the appropriate venue in this blog. I also find that writing for a wider, unknown audience forces me to communicate more extensively than if I write for myself. This will ensure that I recall my own intent and ambition for a project when I look back at a project or documentation after an extended break.

Some topics that are of current interest to me include:

  • Artificial intelligence - My short-terms plans include exploring current architectures, frameworks, and mechanisms for working with intelligent agents. These agents include both software agents as well as hardware robots. My goals for this research include finding scalable means for developing, training, testing and deploying a large number of these agents.
  • Machine learning - My interests in ML include reinforcement learning and neural networks and their applications to learning in robotics. I am also looking to pick up some skills in natural language processing and synthesis for applications in news technology.
  • Evolutionary computation - Evolutionary algorithms, artificial life, and agent-based modeling were the primary motifs of my graduate studies. I am planning to revisit some portions of my research. I am also investigating the current state of the art in software platforms and frameworks for exploring evolutionary algorithms and agent-based models. If it is still similar to what it was a decade ago, I might take some time and develop a scalable platform.
  • Robotics and internet of things - Primary applications of research in robotics will be to navigation and path planning in structured and semi-structured environments. My interests in the internet of things include learning and investigating new SoC platforms that can be used as controllers for robots.
  • General Computer Science - From time to time, I wish to take small breaks from my long-term research to learn and implement some new aspect of computer science.

An expert is one who knows more and more about less and less until he knows absolutely everything about nothing. – Nicholas Murray

Philosophers are people who know less and less about more and more, until they know nothing about everything. – Konrad Lorenz

My goal in life is to be an expert philosopher and I believe that path to achieve that is to keep learning more and more about more and more until I know absolutely everything about everything.

About Me

Shivakar Vulli Hello there, I am Shivakar Vulli.

I am the co-founder of Cogizo Technologies, together with a long-time friend Bharath Muppireddy, where we are trying to solve problems at the intersection of a number of interesting fields.

Before Cogizo, I worked at In4mation Insights as the Managing Director of Information Engineering and High Performance Computing. At In4mation Insights, I worked with a number of Fortune 200 companies, including, CPG/FMCG retailers and manufacturers, B2B and B2C online retailers, restaurant chains, hotel groups and aftermarket retailers in designing and developing innovative architectures and SaaS products for price, media & promotion planning and optimization, marketing mix analysis, site location management, historical data management, sensory analysis and new product forecasting.

My professional education includes degrees in Mechanical Engineering and Computer Science and I find myself drawn to multi-disciplinary problems. During graduate studies, I had the privilege of working on a number of projects with a concentration in artificial intelligence, robotics, evolutionary algorithms, agent-based models, multi-objective optimization, artificial life, computer vision and anomaly detection. More details on my education and experience are available in my resume.

In general, I am interested in anything related to science, technology, and philosophy. However, I am currently passionate about three directions for R&D:

  • Artificial intelligence, machine learning and evolutionary computation. I am particularly interested in Generative AI and its applications to product design.
  • Natural language text & speech processing and synthesis with applications to automated news analysis.
  • Robotic networks and internet of things with applications to navigation in structured and semi-structured warehouse environments.

I am always happy to make some time to be involved in interesting projects. So, if you would like to get in touch regarding contract work, speaking engagements, discuss on any fields that I might be interested in or have a few comments or questions, please feel free to drop an email at svulli@shivakar.com.