javacv
javacv copied to clipboard
How to assign specific video bitrate and audio bitrate values to a video file
How to assign specific video bitrate and audio bitrate values ?
When I try to the code in the below, I take wrong response.
void processVideo() {
System.setProperty("org.bytedeco.javacpp.logger.debug", "true");
FFmpegLogCallback.set();
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("<location from root>\\input.mp4");
grabber.start();
grabber.setAudioCodec(AV_CODEC_ID_AAC);
grabber.setVideoCodec(AV_CODEC_ID_H264);
FrameRecorder recorder = new FFmpegFrameRecorder("<location from root>\\output.mp4", grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoCodec(AV_CODEC_ID_H264);
recorder.setVideoBitrate(100000);
recorder.setAudioCodec(AV_CODEC_ID_AAC);
recorder.setAudioBitrate(100000);
recorder.start();
Frame frame;
while ((frame = grabber.grabFrame()) != null) {
recorder.record(frame);
}
recorder.stop();
grabber.stop();
}
Instance :
I have a video file that has extension MP4. It (input.mp4) has :
- Size : 6.11MB(6,408,875 bytes)
- Total Bitrate : 145kbps
- Data Rate : 18 kbps
- Audio bitrate : 127kbps
- Frame Rate : 25.00 frames/second
- Length: 5m 37s
- Frame width : 720
- Frame height : 720
- Audio sample rate : 44.100 kHz
Output (output.mp4) has :
- Size : 5.98MB(6,271,125 bytes)
- Total Bitrate : 143kbps
- Data Rate : 43 kbps
- Audio bitrate : 100kbps
- Frame Rate : 25.00 frames/second
- Length: 5m 37s
- Frame width : 720
- Frame height : 720
- Audio sample rate : 44.100 kHz
Just curious, what's the result when its done outside of the javacv with ffmpeg-cli
Something in the lines of:
ffmpeg -i input.mp4 -b:v 100k -b:a 100k output.mp4
My guess is you need to do the two-pass encoding. See the H.264 encoding guide.
when mentioned command was run with the file (input.mp4) which I had shared in issue description, generated file has
Size : 7.80MB(8,182,272 bytes) Total Bitrate : 186kbps Data Rate : 86kbps Audio bitrate : 100kbps Frame Rate : 25.00 frames/second Length: 5m 37s Frame width : 720 Frame height : 720 Audio sample rate : 44.100 kHz
I see. The bitrates specified in this command and my guess on the recorder are target bitrates, not necessarily guaranteed. I suggest trying the two-pass encoding on ffmpeg-cli and if that works we can think how we can do that on javacv.
Check out the Two-Pass H264 Encoding Guide Maybe try running these two and see if that helps.
ffmpeg -y -i input -c:v libx264 -b:v 100k -pass 1 -an -f null /dev/null && \
ffmpeg -i input -c:v libx264 -b:v 100k -pass 2 -c:a aac -b:a 100k output.mp4
I've tried this before, result file has
Audio Bitrate : 100k Video Bitrate : 100k
Size : 8.00MB(8,396,219 bytes) Total Bitrate : 191kbps (Data Rate: 91 kbps) Audio bitrate : 100kbps Frame Rate : 25.00 frames/second Length: 5m 37s Frame width : 720 Frame height : 720 Audio sample rate : 44.100 kHz
When I tried with different values, I took different responses again.
Instance 1 : Audio Bitrate : 100K Video Bitrate : 50K
Size : 6.65MB(6,980,614 bytes) Total Bitrate : 158kbps (Data Rate : 50kbps) Audio bitrate : 100kbps Frame Rate : 25.00 frames/second Length: 5m 37s Frame width : 720 Frame height : 720 Audio sample rate : 44.100 kHz
Instance 2 : Audio Bitrate : 80K Video Bitrate : 80K
Size : 6.65MB(6,980,614 bytes) Total Bitrate : 169kbps (Data Rate: 89kbps) Audio bitrate : 80kbps Frame Rate : 25.00 frames/second Length: 5m 37s Frame width : 720 Frame height : 720 Audio sample rate : 44.100 kHz
Instance 3 : Audio Bitrate : 150K Video Bitrate : 80K
Size : 10.00MB(10,510,700 bytes) Total Bitrate : 242kbps (Data Rate : 89kbps) Audio bitrate : 152kbps Frame Rate : 25.00 frames/second Length: 5m 37s Frame width : 720 Frame height : 720 Audio sample rate : 44.100 kHz
Audio bitrate is being set properly to my gave value. In the other hand, if video bitrate isn't equal to data rate, video bitrate isn't stabil.
I decided to change video file which I used and I did re test again and I look data rate of video
My New File Properties : Size : 9.82MB(10,301,752 bytes) Total Bitrate : 490kbps Data Rate : 394kbps Audio bitrate : 152kbps Frame Rate : 30.00 frames/second Length: 2m 46s Frame width : 640 Frame height : 360 Audio sample rate : 44.100 kHz
Instance 1 : Audio Bitrate : 100k Video Bitrate : 100k
Size : 4.18MB(4,384,412 bytes) Total Bitrate : 191kbps (Data rate : 91kpbs) Audio bitrate : 100kbps Frame Rate : 30.00 frames/second Length: 2m 46s Frame width : 720 Frame height : 360 Audio sample rate : 44.100 kHz
Instance 2 : Audio Bitrate : 100k Video Bitrate : 200k
Size : 6.16MB(6,467,715 bytes) Total Bitrate : 301kbps (Data rate : 200kpbs) Audio bitrate : 100kbps Frame Rate : 30.00 frames/second Length: 2m 46s Frame width : 720 Frame height : 360 Audio sample rate : 44.100 kHz
Instance 3 : Audio Bitrate : 100k Video Bitrate : 300k
Size : 8.15MB(8,553,527 bytes) Total Bitrate : 401kbps (Data rate : 300kpbs) Audio bitrate : 100kbps Frame Rate : 30.00 frames/second Length: 2m 46s Frame width : 720 Frame height : 360 Audio sample rate : 44.100 kHz
When I checked video bitrate of a video in the internet, I had found total bitrate equals to video bitrate. I think Data rate is equals to video bitrate. If it so, shared command works very well.
@Nycrera , I have two machine which are linux & windows. In the windows machine's video details page, total bitrate is equal to data rate + audio bitrate therefore I was wrong it. The command that you've shared before (in Two-Pass H264 Encoding Guide Part) works perfectly. How can it be implemented ?
BTW, libx264 is probably better than OpenH264 at estimating bitrates even in the case of single-pass encoding, so make sure to try that one.
When I changed video codec with this this setVideoCodecName("libx264"), generated file has :
For : Audio Bitrate : 100k Video Bitrate : 20k
Size : 11.8MB(12,437,845 bytes) Total Bitrate : 289kbps (Data rate : 189kpbs) Audio bitrate : 100kbps Frame Rate : 30.00 frames/second Length: 5m 37s Frame width : 720 Frame height : 720 Audio sample rate : 44.100 kHz
In the other hand, with AV_CODEC_ID_H264
Size : 5.62MB(12,437,845 bytes) Total Bitrate : 134kbps (Data rate : 33kpbs) Audio bitrate : 100kbps Frame Rate : 30.00 frames/second Length: 5m 37s Frame width : 720 Frame height : 720 Audio sample rate : 44.100 kHz
If I didn't miss anything, AV_CODEC_ID_H264 usage looks good.
About two-pass encoding with javacv I am just as clueless as you.
I would use the bundled FFmpeg if the only purpose is to get a specific file size (eg. specific bitrates) Maybe something in the lines of:
String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
ProcessBuilder pbFirstPass = new ProcessBuilder(ffmpeg, "-i", "input.mp4", "-c:v", "libx264", "-b:v", "100k", "-pass", "1", "-an", "-f", "null", "/dev/null");
ProcessBuilder pbSecondPass = new ProcessBuilder(ffmpeg, "-i", "input.mp4", "-c:v", "libx264", "-b:v", "100k", "-pass", "2", "-c:a", "aac", "-b:a","100k" , "output.mp4");
pbFirstPass.inheritIO().start().waitFor();
pbSecondPass.inheritIO().start().waitFor();
You could also try encoding with an FFMpegFrameRecorder twice by setting the options -pass as 1 and 2 with .setOption() and see if that works.
I am somewhat speculating as I can not reproduce results such as yours.
I got it, thanks @Nycrera but I have some doubts about ProcessBuilder usage because value areas can be defined dynamic. I can give other values except 100k. Maybe 200k, 400k etc. If I write in this format, It can be vulnerability and It will be brought extra work load(docker container update, updating file permissions for embedded ffmpeg) therefore I don't want to use it. I just want to do it with JavaCV FFMPEG. Is there any possible way to do with JavaCV FFMPEG ?
Additionally, when I use one pass way that is first way I used, file size may be weird values. For example my video file's size is 4MB. After operation, It is 2MB 1MB etc. I know it's normal but I want to set it specific value which I determined.
I found this command ffmpeg -i input.mp4 -fs 100K output.mp4
This command can be written with JavaCV FFMPEG?
You can set the values dynamically, you can dynamically create strings after all. But I understand your other concerns.
I am not sure about it but like I said try using setOption(). Something like:
recorder.setOption("pass","1");
Then process the first pass, delete the first encoded output, and hope that this creates the ffmpeglog needed for second pass.
recorder.setOption("pass","2");
Run the second encoding like so and check if this output meets your set filesize.
About -fs option, a quick look to FFMpeg Docs explains -fs as a file size limit for the output file. No further chunk of bytes is written after the limit is exceeded. As such this may not be what you are looking for.
But if it is, then I think you can set the -fs option just like so:
recorder.setOption("fs","100K");