I'm having a hard time getting the right duration and the exact framerate in Jcodec.
My situation is I have a app that shows an array of bitmaps, wherein the user can change its frame rate like 1fps, 5fps, 32fps, all I did was 1000/fps. so 1fps will show 1 bitmap every 1 second, 2fps: 2bitmap and so on, in short the user is the one that supplies the frame rate.I found this but I can't get the right formula to it.
And another thing, about the duration. What if I want 1fps and I have 16 bitmaps. JCodec should produce a 16 seconds video.
How can I achieve that? lets say that the bitmaps will be dynamic. Base on what I understand, Jcodec relies on hard coded duration. not by the number of frames it has encoded and converted to MP4.
Thanks in advance.
Had a hard time finding this myself. Took some searching through the API.
FileChooser fc = new FileChooser();
File file = fc.showOpenDialog(null);
SeekableByteChannel bc = NIOUtils.readableFileChannel(file);
MP4Demuxer dm = new MP4Demuxer(bc);
DemuxerTrack vt = dm.getVideoTrack();
double frameRate = vt.getMeta().getTotalFrames()/vt.getMeta().getTotalDuration();
Related
I've developed an Android app that allows user to create boomerang-alike mp4 video. This video consists of 10 still images being played back and forth quite fast. I know that such video (boomerang effect) can be easily looped from single video file while playing it, but I really need to create a mp4 video that would essentially contain already prepared boomerang video. The output video can be downloaded and played by user on any external player (over which obviously I don't have any control).
For that purpose currently I create a video from images in a loop. The loop starts from 1st picture and goes to 10th picture with 0.25 sec delay between frames, then goes back from 10th to 1st including delay. And there is 5 of those loops, which essentialy means creating a single video from 5 * 10 * 2 = 100 images. I know it's kinda ridiculous, so the time that it takes to prepare this video is riduculous as well (around 1:40 min).
What solution could you recommend assuming that the output video really has to consist of 5 loops back-and-forth? I've thought about creating single loop video (20 pictures) and then create final output video by concatenating it 5 times. But could it be any good? I'm trying to find an efficient yet understandable for a beginner Android programmer way.
You can use FFMPEG to Create boomerang like video below is a simple example code :-
ffmpeg -i input_loop.mp4 -filter_complex "[0]reverse[r];[0][r]concat,loop=5:250,setpts=N/55/TB" output_looped_video.mp4
1.5 seconds of video file as input named input_loop.mp4
n loop=5:250, 5 is number of loops, 250 is frame rate x double length of clip. The setpts is applied to avoid frame drops, and the value 25 should be replaced with the framerate of the clip
setpts=N/<VALUE>/TB" you can alter value according to your need
increase value to speed up boomerang effect
decrease value to slow down boomerang effect
I was looking for a way to create a boomerang video and found a pretty cool example of how to do it on GitHub.
You create the video by using the FFMPEG library org.bytedeco.javacpp-presets to clone the frames.
https://github.com/trantrungduc/boomerang-android
This is the place in code in which you can customize the video loop:
for (int k = 0; k < 3; k++) {
for (Frame frame1 : loop) {
frecorder.record(frame1);
}
for (int i=loop.size()-1;i>=0;i--){
frecorder.record(loop.get(i));
}
}
So, I'm trying to cut a 1 minute 29 seconds video into clips of 30 seconds each.
The expected output is 30sec,30sec,29sec.
The result is 35sec,29sec,23sec.
This is my code -
ArrayList<String> commandList = new ArrayList<>();
commandList.add("-ss");
commandList.add("00:00:00");
commandList.add("-i");
commandList.add(videoPath);
commandList.add("-c");
commandList.add("copy");
commandList.add("-f");
commandList.add("segment");
commandList.add("-segment_time");
commandList.add("00:00:30");
commandList.add(TEST.getAbsolutePath());
String[] command = commandList.toArray(new String[commandList.size()]);
execFFmpegBinary(command);
Any idea what I'm doing wrong? I read somewhere that if at a particular position a keyframe doesn't exists it seeks to position of the nearest keyframe.
Any solution or guidance will help me. Thank you in advance.
FFmpeg will start segments on keyframes. When using -codec copy there is no transcode, so It must use the existing keyframes. There is no keyframe at 30s, so it cuts at the next one.
I am trying to save image sequences with fixed framerates (preferably up to 30) on an android device with FULL capability for camera2 (Galaxy S7), but I am unable to a) get a steady framerate, b) reach even 20fps (with jpeg encoding). I already included the suggestions from Android camera2 capture burst is too slow.
The minimum frame duration for JPEG is 33.33 milliseconds (for resolutions below 1920x1080) according to
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputMinFrameDuration(ImageFormat.JPEG, size);
and the stallduration is 0ms for every size (similar for YUV_420_888).
My capture builder looks as follows:
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
captureBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, _exp_time);
captureBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
captureBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, _iso_value);
captureBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, _foc_dist);
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CONTROL_AF_MODE_OFF);
captureBuilder.set(CaptureRequest.CONTROL_AWB_MODE, _wb_value);
// https://stackoverflow.com/questions/29265126/android-camera2-capture-burst-is-too-slow
captureBuilder.set(CaptureRequest.EDGE_MODE,CaptureRequest.EDGE_MODE_OFF);
captureBuilder.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE, CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_OFF);
captureBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, CaptureRequest.NOISE_REDUCTION_MODE_OFF);
captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
// Orientation
int rotation = getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,ORIENTATIONS.get(rotation));
Focus distance is set to 0.0 (inf), iso is set to 100, exposure-time 5ms. Whitebalance can be set to OFF/AUTO/ANY VALUE, it does not impact the times below.
I start the capture session with the following command:
session.setRepeatingRequest(_capReq.build(), captureListener, mBackgroundHandler);
Note: It does not make a difference if I request RepeatingRequest or RepeatingBurst..
In the preview (only texture surface attached), everything is at 30fps.
However, as soon as I attach an image reader (listener running on HandlerThread) which I instantiate like follows (without saving, only measuring time between frames):
reader = ImageReader.newInstance(_img_width, _img_height, ImageFormat.JPEG, 2);
reader.setOnImageAvailableListener(readerListener, mBackgroundHandler);
With time-measuring code:
ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
#Override
public void onImageAvailable(ImageReader myreader) {
Image image = null;
image = myreader.acquireNextImage();
if (image == null) {
return;
}
long curr = image.getTimestamp();
Log.d("curr- _last_ts", "" + ((curr - last_ts) / 1000000) + " ms");
last_ts = curr;
image.close();
}
}
I get periodically repeating time differences like this:
99 ms - 66 ms - 66 ms - 99 ms - 66 ms - 66 ms ...
I do not understand why these take double or triple the time that the stream configuration map advertised for jpeg? The exposure time is well below the frame duration of 33ms. Is there some other internal processing happening that I am not aware of?
I tried the same for the YUV_420_888 format, which resulted in constant time-differences of 33ms. The problem I have here is that the cellphone lacks the bandwidth to store the images fast enough (I tried the method described in How to save a YUV_420_888 image?). If you know of any method to compress or encode these images fast enough myself, please let me know.
Edit: From the documentation of getOutputStallDuration: "In other words, using a repeating YUV request would result in a steady frame rate (let's say it's 30 FPS). If a single JPEG request is submitted periodically, the frame rate will stay at 30 FPS (as long as we wait for the previous JPEG to return each time). If we try to submit a repeating YUV + JPEG request, then the frame rate will drop from 30 FPS." Does this imply that I need to periodically request a single capture()?
Edit2: From https://developer.android.com/reference/android/hardware/camera2/CaptureRequest.html: "The necessary information for the application, given the model above, is provided via the android.scaler.streamConfigurationMap field using getOutputMinFrameDuration(int, Size). These are used to determine the maximum frame rate / minimum frame duration that is possible for a given stream configuration.
Specifically, the application can use the following rules to determine the minimum frame duration it can request from the camera device:
Let the set of currently configured input/output streams be called S.
Find the minimum frame durations for each stream in S, by looking it up in android.scaler.streamConfigurationMap using getOutputMinFrameDuration(int, Size) (with its respective size/format). Let this set of frame durations be called F.
For any given request R, the minimum frame duration allowed for R is the maximum out of all values in F. Let the streams used in R be called S_r.
If none of the streams in S_r have a stall time (listed in getOutputStallDuration(int, Size) using its respective size/format), then the frame duration in F determines the steady state frame rate that the application will get if it uses R as a repeating request."
The JPEG output is by way not the fastest way to fetch frames. You can accomplish this a lot faster by drawing the frames directly onto a Quad using OpenGL.
For burst capture, a faster solution would be capturing the images to RAM without encoding them, then encoding and saving them asynchronously.
On this website you can find a lot of excellent code related to android multimedia in general.
This specific program uses OpenGL to fetch the pixel data from an MPEG video. It's not difficult to use the camera as input instead of a video. You can basically use the texture used in the CodecOutputSurface class from the mentioned program as output texture for your capture request.
A possible solution I found consists of using and dumping YUV without encoding it as JPEG in combination with a micro Sd-card that is able to save up to 95Mb per second. (I had the misconception that YUV images would be larger, so with a cellphone that has full support for the camera2-pipeline, the write speed should be the limiting factor.
With this setup, I was able to achieve the following stable rates:
1920x1080, 15fps (approx. 4Mb * 15 == 60Mb/sec)
960x720, 30fps. (approx. 1.5Mb * 30 == 45Mb/sec)
I then encode the images offline from YUV to PNG using a python script.
To be specific, I'll present my question in an example:
Say at t = 0 ms, a frame was completed and became visible to the user on the screen. From that point on, I began the work to draw the next frame. However, the work took too long that I missed the frame's due time of t = 16 ms. If finally this next frame was ready at t = 23 ms. When would it actually be visible to the user? t = 23 ms or t = 32 ms (at the next drawing "heart beat" if any)?
And also, where in the Android source code can I find the answer myself?
You actually get 16.666ms per frame, so if you aren't ready to draw at that point then the next attempt would be made at 34ms. Colt from Google has a good video explaining this actually. https://www.youtube.com/watch?v=HXQhu6qfTVU
I'm developing an Android video app where I need to get the current frame number of the video being displayed while in pause mode.
I need to send my Server the frame number currently paused in video and get back a list of items regarding that frame/time, right now I'm sending the current paused time in milliseconds, but it doesn't work quite well, because the Server compare the time sent to a specific frame it calculated, based on the time, but sometimes the comparison is not exact.
I know you can get a bitmap from that frame if you use MediaMetaDataRetriever, and I did it but it returns bitmap image and what I need is an index.
I'm using ExoPlayer (I need that feature for MP4 and for HLS, too, if that matters).
Is there a way to get that info from the video?
I post a solution to my problem, In order to get the exact frame time I simply extended MediaCodecVideoTrackRenderer.java class from ExoPlayer library and used the value of lastOutputBufferTimestamp which is in function:
#Override
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs,
MediaCodec codec, ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex,
boolean shouldSkip) {
boolean processed = super.processOutputBuffer(positionUs, elapsedRealtimeUs, codec, buffer,
bufferInfo, bufferIndex, shouldSkip);
if (!shouldSkip && processed) {
lastOutputBufferTimestamp = bufferInfo.presentationTimeUs;
}
return processed;
}
It does give me the exact time and not a rounded time from, last say, mPlayer.getDuration() or something like that.
If you have a constant FPS in your video you can calculate that by division and get the number of the frame.
It was simply enough for me to know the exact frame time.
I'm using ExoPlayer version r1.5.3 so I don't know if this solution will work for newer version since code has probably changed.