Commit
- estimate FPS before video recording begins, and use that in the Encoder() arg
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,7 @@ public class VideoCameraActivity extends CameraBaseActivity { | |
private String mFileBasePath = null; | ||
private boolean mIsRecording = false; | ||
|
||
private ArrayDeque<byte[]> mFrameQ = null; | ||
private ArrayDeque<VideoFrame> mFrameQ = null; | ||
|
||
private int mLastWidth = -1; | ||
private int mLastHeight = -1; | ||
|
@@ -55,16 +55,16 @@ public class VideoCameraActivity extends CameraBaseActivity { | |
private byte[] audioData; | ||
private AudioRecord audioRecord; | ||
|
||
private int mFramesTotal = 0; | ||
private int mFPS = 0; | ||
|
||
private boolean mPreCompressFrames = true; | ||
private OutputStream outputStreamAudio; | ||
private info.guardianproject.iocipher.File fileAudio; | ||
|
||
private int frameCounter = 0; | ||
private long start = 0; | ||
|
||
private long lastTime = 0; | ||
private int mFramesTotal = 0; | ||
private int mFPS = 15; //default is 15fps | ||
|
||
private boolean isRequest = false; | ||
|
||
private boolean mInTopHalf = false; | ||
|
@@ -115,16 +115,15 @@ public boolean onTouch(View v, MotionEvent ev) { | |
break; | ||
case MotionEvent.ACTION_CANCEL: | ||
case MotionEvent.ACTION_UP: | ||
if (mIsOnClick) { | ||
if (!mIsRecording) { | ||
|
||
//take a picture | ||
mPreviewing = false; | ||
camera.takePicture(null, null, this); | ||
handler.removeCallbacks(mLongPressed); | ||
|
||
} | ||
|
||
if (mIsRecording) | ||
else | ||
{ | ||
stopRecording(); | ||
} | ||
|
@@ -143,8 +142,6 @@ public boolean onTouch(View v, MotionEvent ev) { | |
if (mIsOnClick && (Math.abs(mDownX - ev.getX()) > SCROLL_THRESHOLD || Math.abs(mDownY - ev.getY()) > SCROLL_THRESHOLD)) { | ||
mIsOnClick = false; | ||
|
||
|
||
|
||
} | ||
break; | ||
default: | ||
|
@@ -187,10 +184,13 @@ public void onClick(View view) { | |
|
||
private void startRecording () | ||
{ | ||
mFrameQ = new ArrayDeque<byte[]>(); | ||
mFrameQ = new ArrayDeque<VideoFrame>(); | ||
|
||
mFramesTotal = 0; | ||
|
||
frameCounter = 0; | ||
|
||
lastTime = System.currentTimeMillis(); | ||
|
||
String fileName = "video" + new java.util.Date().getTime() + ".mov"; | ||
info.guardianproject.iocipher.File fileOut = new info.guardianproject.iocipher.File(mFileBasePath,fileName); | ||
|
||
|
@@ -202,7 +202,10 @@ private void startRecording () | |
else | ||
initAudio(fileOut.getAbsolutePath()+".pcm"); | ||
|
||
new Encoder(fileOut).start(); | ||
boolean withEmbeddedAudio = false; | ||
|
||
Encoder encoder = new Encoder(fileOut,mFPS,withEmbeddedAudio); | ||
encoder.start(); | ||
//start capture | ||
startAudioRecording(); | ||
|
||
|
@@ -266,62 +269,67 @@ public void run() { | |
@Override | ||
public void onPreviewFrame(byte[] data, Camera camera) { | ||
|
||
if (mIsRecording && mFrameQ != null) | ||
//even when not recording, we'll compress frames in order to estimate our FPS | ||
|
||
Camera.Parameters parameters = camera.getParameters(); | ||
mLastWidth = parameters.getPreviewSize().width; | ||
mLastHeight = parameters.getPreviewSize().height; | ||
|
||
if (mRotation > 0) //flip height and width | ||
{ | ||
|
||
Camera.Parameters parameters = camera.getParameters(); | ||
mLastWidth = parameters.getPreviewSize().width; | ||
mLastHeight = parameters.getPreviewSize().height; | ||
|
||
if (mRotation > 0) //flip height and width | ||
{ | ||
mLastWidth =parameters.getPreviewSize().height; | ||
mLastHeight =parameters.getPreviewSize().width; | ||
} | ||
|
||
mPreviewFormat = parameters.getPreviewFormat(); | ||
|
||
byte[] dataResult = data; | ||
|
||
if (mPreCompressFrames) | ||
mLastWidth =parameters.getPreviewSize().height; | ||
mLastHeight =parameters.getPreviewSize().width; | ||
} | ||
|
||
mPreviewFormat = parameters.getPreviewFormat(); | ||
|
||
byte[] dataResult = data; | ||
|
||
if (mPreCompressFrames) | ||
{ | ||
if (mRotation > 0) | ||
{ | ||
if (mRotation > 0) | ||
{ | ||
dataResult = rotateYUV420Degree90(data,mLastHeight,mLastWidth); | ||
|
||
if (getCameraDirection() == CameraInfo.CAMERA_FACING_FRONT) | ||
{ | ||
dataResult = rotateYUV420Degree90(dataResult,mLastWidth,mLastHeight); | ||
dataResult = rotateYUV420Degree90(dataResult,mLastHeight,mLastWidth); | ||
} | ||
|
||
} | ||
dataResult = rotateYUV420Degree90(data,mLastHeight,mLastWidth); | ||
|
||
YuvImage yuv = new YuvImage(dataResult, mPreviewFormat, mLastWidth, mLastHeight, null); | ||
ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||
yuv.compressToJpeg(new Rect(0, 0, mLastWidth, mLastHeight), MediaConstants.sJpegQuality, out); | ||
dataResult = out.toByteArray(); | ||
} | ||
|
||
if (getCameraDirection() == CameraInfo.CAMERA_FACING_FRONT) | ||
{ | ||
dataResult = rotateYUV420Degree90(dataResult,mLastWidth,mLastHeight); | ||
dataResult = rotateYUV420Degree90(dataResult,mLastHeight,mLastWidth); | ||
} | ||
|
||
} | ||
|
||
YuvImage yuv = new YuvImage(dataResult, mPreviewFormat, mLastWidth, mLastHeight, null); | ||
ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||
yuv.compressToJpeg(new Rect(0, 0, mLastWidth, mLastHeight), MediaConstants.sJpegQuality, out); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
n8fr8
via email
Author
Owner
|
||
dataResult = out.toByteArray(); | ||
} | ||
|
||
|
||
if (mIsRecording && mFrameQ != null) | ||
synchronized (mFrameQ) | ||
{ | ||
if (data != null) | ||
{ | ||
mFrameQ.add(dataResult); | ||
mFramesTotal++; | ||
|
||
frameCounter++; | ||
if((System.currentTimeMillis() - start) >= 1000) { | ||
mFPS = frameCounter; | ||
frameCounter = 0; | ||
start = System.currentTimeMillis(); | ||
} | ||
VideoFrame vf = new VideoFrame(); | ||
vf.image = dataResult; | ||
vf.duration = System.currentTimeMillis() - lastTime; | ||
vf.fps = mFPS; | ||
|
||
mFrameQ.add(vf); | ||
|
||
mFramesTotal++; | ||
|
||
} | ||
} | ||
|
||
|
||
} | ||
frameCounter++; | ||
if((System.currentTimeMillis() - start) >= 1000) { | ||
mFPS = frameCounter; | ||
frameCounter = 0; | ||
start = System.currentTimeMillis(); | ||
} | ||
|
||
} | ||
|
||
|
@@ -353,22 +361,32 @@ private byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight | |
return yuv; | ||
} | ||
|
||
private class VideoFrame | ||
{ | ||
byte[] image; | ||
long fps; | ||
long duration; | ||
} | ||
|
||
private class Encoder extends Thread { | ||
private static final String TAG = "ENCODER"; | ||
|
||
private File fileOut; | ||
private FileOutputStream fos; | ||
|
||
public Encoder (File fileOut) throws IOException | ||
public Encoder (File fileOut, int baseFramesPerSecond, boolean withEmbeddedAudio) throws IOException | ||
{ | ||
this.fileOut = fileOut; | ||
|
||
fos = new info.guardianproject.iocipher.FileOutputStream(fileOut); | ||
SeekableByteChannel sbc = new IOCipherFileChannelWrapper(fos.getChannel()); | ||
|
||
org.jcodec.common.AudioFormat af = null;//new org.jcodec.common.AudioFormat(org.jcodec.common.AudioFormat.MONO_S16_LE(MediaConstants.sAudioSampleRate)); | ||
org.jcodec.common.AudioFormat af = null; | ||
|
||
if (withEmbeddedAudio) | ||
af = new org.jcodec.common.AudioFormat(org.jcodec.common.AudioFormat.MONO_S16_LE(MediaConstants.sAudioSampleRate)); | ||
|
||
muxer = new ImageToMJPEGMOVMuxer(sbc,af); | ||
muxer = new ImageToMJPEGMOVMuxer(sbc,af,baseFramesPerSecond); | ||
} | ||
|
||
public void run () | ||
|
@@ -380,9 +398,9 @@ public void run () | |
{ | ||
if (mFrameQ.peek() != null) | ||
{ | ||
byte[] data = mFrameQ.pop(); | ||
VideoFrame vf = mFrameQ.pop(); | ||
|
||
muxer.addFrame(mLastWidth, mLastHeight, ByteBuffer.wrap(data),mFPS); | ||
muxer.addFrame(mLastWidth, mLastHeight, ByteBuffer.wrap(vf.image),vf.fps,vf.duration); | ||
|
||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,36 +37,40 @@ public class ImageToMJPEGMOVMuxer { | |
private String imageType = "jpeg "; //or "png "; | ||
private AudioFormat af = null; | ||
|
||
private int timeScale = 20; | ||
private int framesPerSecond = -1; | ||
|
||
public ImageToMJPEGMOVMuxer(SeekableByteChannel ch, AudioFormat af) throws IOException { | ||
this.ch = ch; | ||
private final static String ENCODER_NAME = "JCODEC"; | ||
|
||
public ImageToMJPEGMOVMuxer(SeekableByteChannel ch, AudioFormat af, int framesPerSecond) throws IOException { | ||
|
||
this.ch = ch; | ||
this.af = af; | ||
this.framesPerSecond = framesPerSecond; | ||
|
||
// Muxer that will store the encoded frames | ||
muxer = new WebOptimizedMP4Muxer(ch, Brand.MOV, 16000); | ||
|
||
// Add video track to muxer | ||
videoTrack = muxer.addTrack(TrackType.VIDEO, timeScale); | ||
videoTrack = muxer.addTrack(TrackType.VIDEO, framesPerSecond); | ||
// videoTrack.setTgtChunkDuration(new Rational(2, 1), Unit.SEC); | ||
|
||
if (af != null) | ||
audioTrack = muxer.addPCMAudioTrack(af); | ||
|
||
} | ||
|
||
public void addFrame(int width, int height, ByteBuffer buff, int timeScaleFPS) throws IOException { | ||
public void addFrame(int width, int height, ByteBuffer buff, long timeScaleFPS, long duration) throws IOException { | ||
if (size == null) { | ||
size = new Size(width,height); | ||
videoTrack.addSampleEntry(MP4Muxer.videoSampleEntry(imageType, size, "JCodec")); | ||
videoTrack.addSampleEntry(MP4Muxer.videoSampleEntry(imageType, size, ENCODER_NAME)); | ||
|
||
if (af != null) | ||
audioTrack.addSampleEntry(MP4Muxer.audioSampleEntry(af)); | ||
} | ||
|
||
// Add packet to video track | ||
|
||
videoTrack.addFrame(new MP4Packet(buff, frameNo, timeScaleFPS, 1, frameNo, true, null, frameNo, 0)); | ||
videoTrack.addFrame(new MP4Packet(buff, frameNo, timeScaleFPS, duration, frameNo, true, null, frameNo, 0)); | ||
This comment has been minimized.
Sorry, something went wrong.
0xPoly
|
||
|
||
frameNo++; | ||
} | ||
|
@@ -77,12 +81,10 @@ public void addAudio (ByteBuffer buffer) throws IOException | |
} | ||
|
||
public void finish() throws IOException { | ||
// Push saved SPS/PPS to a special storage in MP4 | ||
// videoTrack.addSampleEntry(MP4Muxer.videoSampleEntry("png ", size, "JCodec")); | ||
videoTrack.addSampleEntry(MP4Muxer.videoSampleEntry(imageType, size, "JCodec")); | ||
|
||
videoTrack.addSampleEntry(MP4Muxer.videoSampleEntry(imageType, size, ENCODER_NAME)); | ||
|
||
// Write MP4 header and finalize recording | ||
|
||
// Write MP4 header and finalize recording | ||
if (af != null) | ||
audioTrack.addSampleEntry(MP4Muxer.audioSampleEntry(af)); | ||
|
||
|
A little experimentation shows that compressing a high-res JPEG isn't possible quickly on most phones. This results in a poor frame rate. Consider scaling down the YUV before compressing to Jpeg.