しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Web API を使って画像を Motion JPEG で配信する

Raspberry Pi 2 が Azure Blob に保存した画像を Motion JPEG として配信してみたくなったので、まずはローカルに保存している画像を Motion JPEG として返す Web API を作って試してみました。

Motion JPEG - Wikipedia

H.264 で圧縮しても良い気がしますが、エンコードしながら HTTP で出力する難易度がとても高そうだったので、今回はとても簡単な Motion JPEG です。

Web API に用意されている PushStreamContent を使うと、サーバーからのプッシュが簡単に実装できるので、これを利用して JPEG データを定期的に送信することで動画っぽく見せます。

http://blogs.msdn.com/b/henrikn/archive/2012/04/23/using-cookies-with-asp-net-web-api.aspx

中の人がブログで簡単な使い方を紹介してくれています。

要するに PushStreamContent を Content として返すと、レスポンスのストリームを明示的に閉じるか、HTTP 自体が閉じられるまで自由に書き込みできるという話です。動画をバッファリングして配信したり、Server-Sent Events の実装もこれを使えば簡単でしょう。

ちなみに Motion JPEG として配信するための HTTP レスポンスについては、以下の Wiki に書いてあったので参考に実装しました。

[Wiki] Motion JPEG | en.code-bude.net

まずは、ローカルに保存してある画像を出力するだけの API を作ってみました。

public class StreamController : ApiController
{
    private readonly byte[] _newLine = Encoding.UTF8.GetBytes("\r\n");
    private readonly string _boundary = "0123456789";

    public HttpResponseMessage Get()
    {
        var response = Request.CreateResponse();

        response.Content = new PushStreamContent(async (stream, content, context) =>
        {
            foreach (var file in Directory.GetFiles(@"C:\Users\shibayan\Documents\snapshot", "*.jpg"))
            {
                var fileInfo = new FileInfo(file);

                // ヘッダー書き込み
                var header = string.Format("--{0}\r\nContent-Type: image/jpeg\r\nContent-Length: {1}\r\n\r\n", _boundary, fileInfo.Length);
                var headerData = Encoding.UTF8.GetBytes(header);

                await stream.WriteAsync(headerData, 0, headerData.Length);

                // JPEG データ書き込み
                await fileInfo.OpenRead().CopyToAsync(stream);

                // 最後の改行書き込み
                await stream.WriteAsync(_newLine, 0, _newLine.Length);

                // 30fps で頑張ってみるつもり
                await Task.Delay(1000 / 30);
            }
        });

        // PushStreamContent の mediaType に指定すると検証エラーになったのでここで追加
        response.Content.Headers.TryAddWithoutValidation("Content-Type", "multipart/x-mixed-replace;boundary=" + _boundary);

        return response;
    }
}

いつも通り手抜きな感じですが、とりあえずこれで動作を確認してみます。

残念と言うか当然という感じではありますが、世間的には multipart/x-mixed-replace は全く流行らなかったらしく、ブラウザとしては Firefox しか対応していないようです。

問題なく再生出来ました。正確には素早く JPEG を差し替えているだけですが。

他には VLC Media Player を使って、ネットワークストリームとして開くことで再生出来ました。

本来の目的である、Azure Blob に保存している画像を Motion JPEG として配信することに関しては、JPEG を読み込む元を変更するぐらいで対応出来ると思います。