Getting duration of an audio file

I want to get a length of a sound file.

In cocos2d I can get a duration of a sound file by using

static CDSoundEngine* soundEngine = nil;
static CDBufferManager bufferManager = nil;
int voiceID = ;
NSDictionary
tempDict = [soundEngine playVoice:voiceID];
NSLog(“%f”, [tempDict objectForKey:@“kPlayTime”] floatValue]);

but I do not know how to do this with Cocos2dx.

Does anyone know how to get a duration of a sound file? Thank you.

Issue #1514 is created, including some API requirements from other developers. We lack this feature so far.

Walzer Wang wrote:

Issue #1514 is created, including some API requirements from other developers. We lack this feature so far.

ok thanks for your reply :slight_smile:

I have recently had the same requirement, and although I know of a way to do this on android and ios/mac I wanted a way that would work whereever e.g. cocos2d-html5 so I did a little bit of googling and found out that you can get the length of the sound file by using this command (on stackoverflow… cannot find the original :frowning: )

#!/bin/bash
# call me with audiolength.sh directory
# e.g. ./audiolength.sh . 
# or ./audiolength.sh my-mp3-collection

echo "{"
for file in $1/*.caf
do
    echo "\""$file"\":"
    ffmpeg -i "$file" 2>&1 | egrep "Duration"| cut -d ' ' -f 4 | sed s/,//
    echo ","
done
echo "}"

The shell script prints out the file names and the durations of all my files - I then tidy the output up into a json file and use it for looking up audio file durations at runtime…

I know this probably sounds cluncky, but it works really well :slight_smile: and removes the need for relying on the api.

Bumpetibump.

1 Like

The new AudioEngine has getDuration(), but the catch is that it only takes an audioID, not a filename.

2 Likes

I just tried AudioEngine with cocos2d 3.6, but it does not work at all for iOS. All I get from play2d() is -1. Is that broken? Sound formats tested are m4a and wav. These work fine using SimpleAudioEngine. AudioEngine API is funny because you have to play a sound in order to get the duration of it? Well my attempt in implementing a duration method on top of that was to play the sound at 0 volume, retrieve the duration, then immediately stop it, and return the duration. Well it never worked because AudioEngine just could never play my audio files. Shucks.

1 Like

In the latest version getDuration still doesn’t work - or at least not as expected.

It only returns -1. I’m using mp3 files, and tested on iOS and Android.

Code:

auto snd = cocos2d::experimental::AudioEngine::play2d(filename);
float duration = cocos2d::experimental::AudioEngine::getDuration(snd);

Do I do something wrong here?

1 Like

Hey, checking in after a few months since the last post. Any chance we could get this working in Cocos Creator? This seems like a pretty basic, necessary feature.

Hey, it’s been over a month since my last post, and I haven’t heard anything from you guys yet. Were you able to take a look?

We’re dealing with arbitrarily length audio files now, and so we really need this feature. It shouldn’t be too hard to calculate on your end by dividing the number of samples by the sample rate and returning that value.

If there is anything I can do to help you prioritize this, please just let me know. Thanks!

It looks like (at least on iOS), the audioID info objects aren’t stored in the map in AudioEngine.cpp from cocos2d-x-lite on branch v1.4:

float AudioEngine::getDuration(int audioID)
{
    auto it = _audioIDInfoMap.find(audioID);
    if (it != _audioIDInfoMap.end())
    {
        if (it->second.state != AudioState::INITIALZING)
        {
            if (it->second.duration == TIME_UNKNOWN)
            {
                printf("AudioEngine::getDuration(): TIME_UNKNOWN => getDuration impl\n");
                it->second.duration = _audioEngineImpl->getDuration(audioID);
            }
            printf("AudioEngine::getDuration(): Return real duration: %f\n", it->second.duration);
            return it->second.duration;
        } else {
            printf("AudioEngine::getDuration(): Still initializing\n");
        }
    } else {
        printf("AudioEngine::getDuration(): Not in map\n");
    }
    printf("AudioEngine::getDuration(): Return TIME_UNKNOWN\n");
    return TIME_UNKNOWN;
}

Log output:

AudioEngine::getDuration(): Not in map
AudioEngine::getDuration(): Return TIME_UNKNOWN

I’m going to keep investigating this.

Looks like it’s because !lazyInit breaks out of the fake while(0) loop in AudioEngine::play2d()… Looking at that code == SMH :fearful:

Will be submitting a pull request soon with the fix.

I love how a request from 5 years ago still have not being solved :confused:

This is the main reason why cocos2dx is far from being the best engine

No, we have implemented this feature in the new AudioEngine, instead of SimpleAudioEngine.

The API reference is here

Which is nice, BUT:

the new audioEngine doesn’t use the AVAudioPlayer for streaming mp3 through hardware on IOS, it streams them from pcm data, which needs to first be decoded by soft, which takes time and CPU cycles.

I have created A SimpleAudioPlayer that inherits AudioPlayer, and that just overrides the play/pause/resume/setVolume/setTime/getDuration methods from the AudioPlayer to use the SimpleAudioEngine API instead, which use the NSSound class internally( which uses CoreAudio classes for mp3 reading)

I can see a 10% CPU gain and roughly a 2 to 3 times less latency using this method.

It would be nice to have a getDuration method, even in the SimpleAudioEngine, or to provide a way to use the hardware mp3 streamer in the AudioPlayer…

See this issue for reference

After poking around a bit, the NSSound class that is used by the SimpleAudioEngine has a duration property which is quite accurate.

adding a getDuration method to the SimpleAudioEngine is really just a matter of adding the right getters (for ios and mac), which I have done in my cocos fork. It took me around 15 mn.

I’d like to add that there is a size limit on preloading audio files. My workaround is to preload the audio by calling

AudioEngine::play2d(songPath, false, 0.0f, nullptr);

instead of the preload method, and pausing it immediately. And then when its actually time to play the audio, I can do seek to 0, reset the volume to 1, and resume the audio. At this point, calling getDuration will work.

So here is my full wrapper class
https://hastebin.com/dovopenucu.cpp