javacv icon indicating copy to clipboard operation
javacv copied to clipboard

How to assign specific video bitrate and audio bitrate values to a video file

Open hikmet-cakir opened this issue 1 year ago • 11 comments

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

hikmet-cakir avatar Sep 17 '22 11:09 hikmet-cakir

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.

Nycrera avatar Sep 17 '22 15:09 Nycrera

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

hikmet-cakir avatar Sep 17 '22 15:09 hikmet-cakir

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

Nycrera avatar Sep 17 '22 18:09 Nycrera

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.

hikmet-cakir avatar Sep 17 '22 18:09 hikmet-cakir

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.

hikmet-cakir avatar Sep 17 '22 19:09 hikmet-cakir

@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 ?

hikmet-cakir avatar Sep 17 '22 21:09 hikmet-cakir

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.

saudet avatar Sep 18 '22 00:09 saudet

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.

hikmet-cakir avatar Sep 18 '22 07:09 hikmet-cakir

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.

Nycrera avatar Sep 18 '22 18:09 Nycrera

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?

hikmet-cakir avatar Sep 18 '22 19:09 hikmet-cakir

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");

Nycrera avatar Sep 18 '22 22:09 Nycrera