the marathon camera hat ///

In 2009,
I ran a qualifying time for the NYC marathon. SO,
I decided to run it, AND
make a live streaming camera hat that sent photos every 5 seconds to my web site

photo of the hat that was published online in the TImes
photo of the hat that was published online in the TImes

However then I got really excited and ran my first ~4 miles at sub 6min pace — ie I crashed and burned and ran the latter half a whole hour slower the first half…
(and then while switching servers I accidentally deleted the photos)

Maybe someeeeday I will run a marathon again.

In meantime, you can look at the code. It’s five years out of date AND really awful.


package com.android.hat;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.graphics.PixelFormat;

public class Preview extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback, Camera.ShutterCallback, Camera.PictureCallback{
    private static final String TAG = "Hat";
    SurfaceHolder holder;
    Camera camera;

    Preview(Context context) {
        super(context);
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        Log.d(TAG, "Created");
    }

    // Called once the holder is ready
    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, acquire the camera and tell it where
        // to draw.
        camera = Camera.open();
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface will be destroyed when we return, so stop the preview.
        // Because the CameraDevice object is not a shared resource, it's very
        // important to release it when the activity is paused.
        camera.release();
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        Camera.Parameters params = camera.getParameters();
        List sizes = params.getSupportedPictureSizes();
        Size optimalSize = getOptimalPreviewSize(sizes, w, h);
        params.setPreviewSize(optimalSize.width, optimalSize.height);
        params.setPictureSize(optimalSize.width, optimalSize.height);
        params.setPictureFormat(PixelFormat.JPEG);
        params.setJpegQuality(50);
        params.setSceneMode(Camera.Parameters.SCENE_MODE_SPORTS);
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
        params.setRotation(0);
        camera.setParameters(params);
        camera.startPreview();
        Log.d(TAG, "1st Preview called using: " + params.flatten());
    }
    
    public void takePhoto() {
        camera.startPreview();
        camera.setOneShotPreviewCallback(this);
    }

    public void onPreviewFrame(byte[] _data, Camera _camera) {
        camera.takePicture(this, null, this);
        Log.d(TAG, "Photo taken at: " + System.currentTimeMillis());
    }


    /** Handles data for jpeg picture */
    public void onPictureTaken(byte[] data, Camera camera) {
        String fileName = String.format("/sdcard/sd/hat/%d.jpg", System.currentTimeMillis());
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(fileName);
            outStream.write(data);
            outStream.close();
            Log.d(TAG, "picture saved - wrote bytes: " + data.length);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        new Uploader(String.format("%d.jpg", System.currentTimeMillis()), fileName);
        Log.v(TAG, "Took Picture: " + fileName + "is like this big: " + data.length);
    }

    @Override
    public void onShutter() {
        Log.d(TAG, "Shutter'd");
    }
    
    private Size getOptimalPreviewSize(List sizes, int w, int h) {
        if (sizes == null) return null;

        Size optimalSize = null;
        int targetWidth = 800;

        // Try to find an size match 
        for (Size size : sizes) {
            if(size.width > targetWidth){
                optimalSize = size;
                }
            }
        Log.v(TAG, "this is the size: " + optimalSize.width);
        return optimalSize;
        }

}       

package com.android.hat;

import java.io.File;
import java.io.IOException;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;

import android.util.Log;

public class Uploader extends Thread {
    private static final String TAG = "Preview";
    private String localFilePath;
    private String fileNameOnServer;

    public Uploader(String _fileNameOnServer, String _localFilePath) {
        localFilePath = _localFilePath;
        fileNameOnServer = _fileNameOnServer;
        start();
        Log.d(TAG, "Created");
    }

    public void run() {
        String urlString = "http://spencermccormick.com/_php/marathon_jpg.php";
        
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(urlString);
        Log.d(TAG, "Uploading " + localFilePath + " to " + urlString);

        try {
            MultipartEntity entity = new MultipartEntity();
            entity.addPart("uploadedfilename", new StringBody(fileNameOnServer));
            entity.addPart("uploadedfile", new FileBody(new File(localFilePath)));
            httppost.setEntity(entity);
            httpclient.execute(httppost);
        } catch (ClientProtocolException e) {
        } catch (IOException e) {
        }
    }
}