Search code examples
androidmjpeg

Saving video from Android camera as local .mjpeg file


I am working on an application that will save video as an .mpeg file on Android devices. I have been working with vanevery's MJPEG project on Github with only modest success, (https://github.com/vanevery/Android-MJPEG-Video-Capture-FFMPEG/blob/master/src/com/mobvcasting/mjpegffmpeg/MJPEGFFMPEGTest.java).

Here is my code so far:

public class VideoCapture extends Activity implements OnClickListener,    SurfaceHolder.Callback, Camera.PreviewCallback {  
public static final String LOGTAG = "VIDEOCAPTURE";
String szBoundaryStart = "\r\n\r\n--myboundary\r\nContent-Type: image/jpeg\r\nContent-Length: ";
String szBoundaryDeltaTime = "\r\nDelta-time: 110";
String szBoundaryEnd = "\r\n\r\n";  
private SurfaceHolder holder;
private Camera camera;  
private CamcorderProfile camcorderProfile;
boolean bRecording = false;
boolean bPreviewRunning = false;
byte[] previewCallbackBuffer;
File mjpegFile;
FileOutputStream fos;
BufferedOutputStream bos;
Button btnRecord;
Camera.Parameters p;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Date T = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    String szFileName = "videocapture-"+sdf.format(T)+"-";      
    try {       
        mjpegFile = File.createTempFile(szFileName, ".mjpeg", Environment.getExternalStorageDirectory());               
    } catch (Exception e) {
        Log.v(LOGTAG,e.getMessage());
        finish();
    }
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    setContentView(R.layout.main);
    btnRecord = (Button) this.findViewById(R.id.RecordButton);
    btnRecord.setOnClickListener(this);
    camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
    SurfaceView cameraView = (SurfaceView) findViewById(R.id.CameraView);
    holder = cameraView.getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    cameraView.setClickable(true);
    cameraView.setOnClickListener(this);
}
public void onClick(View v) {
    if (bRecording) {
        bRecording = false;
        try {
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        Log.v(LOGTAG, "Recording Stopped");
    } else {
        try {
            fos = new FileOutputStream(mjpegFile);
            bos = new BufferedOutputStream(fos);
            bRecording = true;
            Log.v(LOGTAG, "Recording Started");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
public void surfaceCreated(SurfaceHolder holder) {
    Log.v(LOGTAG, "surfaceCreated");        
    camera = Camera.open();     
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    Log.v(LOGTAG, "surfaceChanged");
    if (!bRecording) {
        if (bPreviewRunning){
            camera.stopPreview();
        } try {
            p = camera.getParameters();
            p.setPreviewSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
            p.setPreviewFrameRate(camcorderProfile.videoFrameRate);
            camera.setParameters(p);
            camera.setPreviewDisplay(holder);
            camera.setPreviewCallback(this);
            Log.v(LOGTAG,"startPreview");
            camera.startPreview();
            bPreviewRunning = true;
        } catch (IOException e) {
            Log.e(LOGTAG,e.getMessage());
            e.printStackTrace();
        }   
    }
}
public void surfaceDestroyed(SurfaceHolder holder) {
    Log.v(LOGTAG, "surfaceDestroyed");
    if (bRecording) {
        bRecording = false;
        try {
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    bPreviewRunning = false;
    camera.release();
    finish();
}
public void onPreviewFrame(byte[] b, Camera c) {
    if (bRecording) {
        // Assuming ImageFormat.NV21
        if (p.getPreviewFormat() == ImageFormat.NV21) {
            Log.v(LOGTAG,"Started Writing Frame");
            try {
                ByteArrayOutputStream jpegByteArrayOutputStream = new ByteArrayOutputStream();
                YuvImage im = new YuvImage(b, ImageFormat.NV21, p.getPreviewSize().width, p.getPreviewSize().height, null);
                Rect r = new Rect(0,0,p.getPreviewSize().width,p.getPreviewSize().height);
                im.compressToJpeg(r, 5, jpegByteArrayOutputStream);                         
                byte[] jpegByteArray = jpegByteArrayOutputStream.toByteArray();
                byte[] boundaryBytes = (szBoundaryStart + jpegByteArray.length + szBoundaryDeltaTime + szBoundaryEnd).getBytes();
                bos.write(boundaryBytes);
                bos.write(jpegByteArray);
                bos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
            Log.v(LOGTAG,"Finished Writing Frame");
        } else {
            Log.v(LOGTAG,"NOT THE RIGHT FORMAT");
        }
    }
}
@Override
public void onConfigurationChanged(Configuration conf) 
{
    super.onConfigurationChanged(conf);  
}   
}

I suspect that the problem may be in the jpeg formatting (parsing) in onPreviewFrame(). Any help or advice would be highly appreciated. Thanks in advance.


Solution

  • It appears that there is a threshold with regards to jpeg quality that is required for an MJPEG file to play. Changing,

    im.compressToJpeg(r, 5, jpegByteArrayOutputStream); 
    

    to,

    im.compressToJpeg(r, 75, jpegByteArrayOutputStream); 
    

    results in a valid MJPEG file.