hendriks73 / ffsampledsp

FFmpeg based service provider for javax.sound.sampled.
GNU Lesser General Public License v2.1
24 stars 5 forks source link

Incorrect framerate returned for an mp3 file #10

Closed mvmn closed 2 years ago

mvmn commented 3 years ago

Framerate is returned incorrectly for MP3 and M4A files. For FLAC correct value of 44100.0 is returned

Example:

import java.io.File;

import javax.swing.JFileChooser;

import com.tagtraum.ffsampledsp.FFAudioFileReader;
import com.tagtraum.ffsampledsp.FFAudioInputStream;

public class TestFramerate {

    public static void main(String args[]) throws Exception {
        JFileChooser jfc = new JFileChooser();
        if (JFileChooser.APPROVE_OPTION == jfc.showOpenDialog(null)) {
            File file = jfc.getSelectedFile();
            FFAudioInputStream audioInputStream = (FFAudioInputStream) new FFAudioFileReader().getAudioInputStream(file);
            System.out.println("Format: " + audioInputStream.getFormat());
            System.out.println("Total frames: " + audioInputStream.getFrameLength());
            System.out.println("FrameSize: " + audioInputStream.getFormat().getFrameSize());
            System.out.println("FrameRate: " + audioInputStream.getFormat().getFrameRate());
            audioInputStream.close();
        }
    }
}

Opening an MP3 file:

[mp3 @ 0x7f7f8cbe2000] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0x7f7f8cbe2000] Estimating duration from bitrate, this may be inaccurate
Format: MPEG-1, Layer 3 44100.0 Hz, 0 bit, stereo, unknown frame size, 38.28125 frames/second, 
Total frames: 12180096
FrameSize: -1
FrameRate: 38.28125

Opening a FLAC file:

Format: FLAC 44100.0 Hz, 16 bit, stereo, unknown frame size, 
Total frames: 188723304
FrameSize: -1
FrameRate: 44100.0

Opening an M4A file:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7fc02ca25c00] stream 0, timescale not set
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7fc02d252400] stream 0, timescale not set
Format: MPEG4 AAC 44100.0 Hz, 16 bit, stereo, unknown frame size, 43.066406 frames/second, 
Total frames: 11808768
FrameSize: -1
FrameRate: 43.066406
mvmn commented 3 years ago

P.S. Why do I need a framerate? I'm trying to figure out the length of file in seconds - by dividing total number of frames (frameLength) by rate of frames per second (frame rate). For FLAC this works fine:

int lengthSeconds = (int) (audioInputStream.getFrameLength() / audioInputStream.getFormat().getFrameRate());

But not for MP3/M4A

Calculating from samplerate needs a frame size in samples, which is always returned as -1. And may not work with VBR.

mvmn commented 3 years ago

P.P.S. Looking at this - https://github.com/hendriks73/ffsampledsp/blob/master/ffsampledsp-java/src/main/java/com/tagtraum/ffsampledsp/FFAudioFileFormat.java - I suppose one workaround to get length (in microseconds) is to do new FFAudioFileReader().getAudioFileFormat(file).properties().get("duration")

hendriks73 commented 3 years ago

Reading compressed audio with the Java Audio API is a two step process:

  1. You read packets (or frames) from the raw stream. For mp3 this means fixed size frames according to the mp3 spec (see for example https://en.wikipedia.org/wiki/MP3#File_structure) with 38.28125 frames/s (each mp3-frame consists of 1152 samples, assuming a sample rate of 44100 Hz we get (1152 / 44100) * 1000 = 26.122449 ms/frame or 38.28125 frames/s). The 1152 samples for frame is true for CBR, not so sure about VBR.
  2. Once you have read the packets, you decode them to PCM. Here you have the frame rate as you would expect it.

See for example https://stackoverflow.com/a/41850901/942774

So the frame rate for both mp3 and m4a is correct.

What kind of results do you get when doing

int lengthSeconds = (int) (audioInputStream.getFrameLength() / audioInputStream.getFormat().getFrameRate());

for mp3 and m4a?

And yes, using the property is definitely the recommended way. Whatever you are trying to do in Java, you can save yourself some grief by simply using what FFmpeg provides. This is even officially documented in https://docs.oracle.com/javase/7/docs/api/javax/sound/sampled/AudioFileFormat.html

mvmn commented 3 years ago

Doesn't seem right. If you look at the output above, the number of frames for an mp3 file that is about 5 minutes long is returned as 12180096

Total frames: 12180096
FrameSize: -1
FrameRate: 38.28125

12180096 frames / 38.28125 frames/sec ~= 318174 seconds, 3.68 days. For a file that is in reality below 5 minutes long.

Something doesn't add up here

hendriks73 commented 3 years ago

Yeah... looks to me like the number of frames is wrong, not the frame rate.

mvmn commented 3 years ago

Updated sample program:

public class TestFramerate {

    public static void main(String args[]) throws Exception {
        JFileChooser jfc = new JFileChooser();
        if (JFileChooser.APPROVE_OPTION == jfc.showOpenDialog(null)) {
            File file = jfc.getSelectedFile();
            AudioFileFormat format = new FFAudioFileReader().getAudioFileFormat(file);
            System.out.println("FF Format Total frames: " + format.getFrameLength());
            System.out.println("FF Format Byte length: " + format.getByteLength());
            System.out.println("FF Format Format properties " + format.properties());
            FFAudioInputStream audioInputStream = (FFAudioInputStream) new FFAudioFileReader().getAudioInputStream(file);
            System.out.println("Format: " + audioInputStream.getFormat());
            System.out.println("Total frames: " + audioInputStream.getFrameLength());
            System.out.println("FrameSize: " + audioInputStream.getFormat().getFrameSize());
            System.out.println("FrameRate: " + audioInputStream.getFormat().getFrameRate());
            System.out.println("Format properties " + audioInputStream.getFormat().properties());

            long lengthSeconds = (long) (audioInputStream.getFrameLength() / audioInputStream.getFormat().getFrameRate());
            System.out.println("Calculate duration (seconds): " + lengthSeconds);
            System.out.println(
                    "Duration from properties (converted to seconds): " + (((Long) format.properties().get("duration")) / 1000000));
            audioInputStream.close();
        }
    }
}

Output for MP3:

[mp3 @ 0x7f8b9f97e800] Estimating duration from bitrate, this may be inaccurate
FF Format Total frames: 12180096
FF Format Byte length: 14204470
FF Format Format properties {duration=276192656}
[mp3 @ 0x7f8ba0929800] Estimating duration from bitrate, this may be inaccurate
Format: MPEG-1, Layer 3 44100.0 Hz, 0 bit, stereo, unknown frame size, 38.28125 frames/second, 
Total frames: 12180096
FrameSize: -1
FrameRate: 38.28125
Format properties {provider=ffsampledsp, bitrate=320000}
Calculate duration (seconds): 318173
Duration from properties (converted to seconds): 276
mvmn commented 3 years ago

the number of frames is wrong

Quite possibly. From the output above:

FF Format Total frames: 12180096
FF Format Byte length: 14204470

These numbers are very close - but a frame must surely be much bigger than one byte.

hendriks73 commented 3 years ago

I have changed this in dev so that getFrameLength() returns the number of whole (floor), compressed frames.