B4A Library [lib] liblame MP3 encoder/decoder

warwound

Expert
Licensed User
liblame MP3 encoder and decoder library for B4A
Here we have a fully functional stereo encoder and somewhat unpredictable decoder...

This B4A library wraps various pieces of 3rd party code that is all licenced under the LGPLv2:
  • liblame is the port of the C LAME library to Android.
  • libwave contains some helper classes required to load and save wav type files.
  • Lame4Android is a complete Android application that puts everything together.
The library is pretty simple to use, it adds a liblameDecoder object and a liblameEncoder object to your project:
  • liblameDecoder
    Events:
    • DecodeComplete(Success As Boolean)
    Methods:
    • Decode ()
      Start the decoding process.
      The event DecodeComplete(Success As Boolean) will be raised on completion.
    • Initialize (EventName As String)
      Initialize the Decoder with an event name.
    • IsInitialized As Boolean
    • SetFiles (InputFilename As String, OutputFilename As String) As Boolean
      Set the source mp3 and destination wav file names.
      Returns True on success, False on error.
  • liblameEncoder
    Events:
    • EncodeComplete(Success As Boolean)
    Methods:
    • Encode ()
      Start the encoding process.
      The event EncodeComplete(Success As Boolean) will be raised on completion.
    • Initialize (EventName As String)
      Initialize the Encoder with an event name.
    • IsInitialized As Boolean
    • SetFiles (InputFilename As String, OutputFilename As String) As Boolean
      Set the source wav and destination mp3 file names.
      The Encoder will default to Quality of Encoder.LAME_PRESET_DEFAULT.
      Returns True on success, False on error.
    • SetQuality (Quality As Int)
      Set the encoding quality.
      Choose from:
      Encoder.LAME_PRESET_DEFAULT
      Encoder.LAME_PRESET_MEDIUM
      Encoder.LAME_PRESET_STANDARD
      Encoder.LAME_PRESET_EXTREME

And some example code:

B4X:
'Activity module
Sub Process_Globals

End Sub

Sub Globals
   Dim DecodeButton, EncodeButton As Button
   Dim LameDecoder As liblameDecoder
   Dim LameEncoder As liblameEncoder
End Sub

Sub Activity_Create(FirstTime As Boolean)

   '   **   the example project does NOT contain file_to_encode.wav   **
   If File.Exists(File.DirRootExternal, "file_to_encode.wav")=False Then
      File.Copy(File.DirAssets, "file_to_encode.wav", File.DirRootExternal, "file_to_encode.wav")
   End If

   LameDecoder.Initialize("LameDecoder")
   LameEncoder.Initialize("LameEncoder")

   DecodeButton.Initialize("DecodeButton")
   DecodeButton.Text="Decoder test"
   Activity.AddView(DecodeButton, 0, 0, 100%x, 75dip)

   EncodeButton.Initialize("EncodeButton")
   EncodeButton.Text="Encoder test"
   Activity.AddView(EncodeButton, 0, 75dip, 100%x, 75dip)

End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub DecodeButton_Click
   Log("liblame decoder test")

   If LameDecoder.SetFiles(File.Combine(File.DirRootExternal, "encoded_file.mp3"), File.Combine(File.DirRootExternal, "decoded_file.wav")) Then
      ProgressDialogShow2("Decoding...", False)
      LameDecoder.Decode
   Else
      Log("An error occurred while calling LameDecoder.SetFiles")
   End If
End Sub

Sub EncodeButton_Click
   Log("liblame encoder test")

   If LameEncoder.SetFiles(File.Combine(File.DirRootExternal, "file_to_encode.wav"), File.Combine(File.DirRootExternal, "encoded_file.mp3")) Then
      '   LameEncoder.SetQuality(LameEncoder.LAME_PRESET_STANDARD)   '   adjust to suit
      ProgressDialogShow2("Encoding...", False)
      LameEncoder.Encode
   Else
      Log("An error occurred while calling LameEncoder.SetFiles")
   End If
End Sub

Sub LameDecoder_DecodeComplete(Success As Boolean)
   ProgressDialogHide
   If Success Then
      Log("liblame Decode SUCCEEDED")
   Else
      Log("liblame Decode FAILED")
   End If
End Sub

Sub LameEncoder_EncodeComplete(Success As Boolean)
   ProgressDialogHide
   If Success Then
      Log("liblame Encode SUCCEEDED")
   Else
      Log("liblame Encode FAILED")
   End If
End Sub
The code copies a non-existent wav file from assets to external memory, to keep the attachment size down i have not included any wav file for you to test with.
Click the Encode button and wait while the wav file is encoded to mp3, you can then click the Decode button and wait while that mp3 file is decoded back to wav format.
Now you can compare the original wav file with the new wav file.
Read on...

The library has it's limits...

It encodes stereo 44100Hz 16 bit wav files to mp3 with no problems.
It decodes these encoded mp3s with no problems.

Encoding wav files with different sample rates and mono wav files generally produces the expected encoded mp3, but trying to decode that mp3 tends to fail, read on...

Decoding mp3s that have been encoded with a different encoder produces very unpredictable results.
Sometime the decoding simply adds or removes a few audio frames from the start or end of the decoded file - the decoder fails to take account of the true amount of frame padding that the mp3 has been encoded with.
The decoder assumes that the file to be decoded has been encoded with default LAME properties such as the amount of frame padding.
Sometimes the decoder produces a small wav file of random noise.
And on at one test file the decoded crashed with a memory error (segment fault) that occured in the native C code.

The liblame library was created for forum member Djembefola, and he has worked around these problems.
(Mainly by ensuring that all files to be decoded have originally been encoded with a compatible version of the original LAME encoder).

So i've made the library available to you all but please note that fixing the problems requires both a knowledge of C and also an understanding of the mp3 encoding and decoding processes - i have neither!
More technical info can be found on the official LAME FAQs web page.

Martin.
 

Attachments

Last edited:

susu

Well-Known Member
Licensed User
Thank you Martin. Your library is very useful :D
 

aeropic

Active Member
Licensed User
Hi,

I have tried this library and it works quite well. (Only tested encoding).
I have coupled it with Erel's Audio streaming example (with .wav file).

Currently it seems that an intermediate .wav file is mandatory

B4X:
 If LameEncoder.SetFiles(File.Combine(File.DirRootExternal, "file_to_encode.wav"), File.Combine(File.DirRootExternal, "encoded_file.mp3")) Then
Is there any way to couple the recordtoWav example with your lameMp3 library ?
What would be nice would be to directly save the audio stream with lame mp3 encoding.

Could be done here, "pushing" the stream to the mp3 encoder ???

B4X:
Sub streamer_RecordBuffer (Buffer() As Byte)
    If recording Then output.WriteBytes(Buffer, 0, Buffer.Length)
    'the above check is required as the last message will arrive after we call StopRecording.
End Sub

Sub btnStartRecording_Click
    buffers.Clear
    output = StartWaveFile(File.DirRootExternal, mFileName, mSampleRate, mMono, mBitRate)
    recording = True
    streamer.StartRecording
    recordingStart = DateTime.Now
    timer1.Enabled = True
    Timer1_Tick
    btnPlay.Enabled = False
End Sub
Thanks
Alain
 

warwound

Expert
Licensed User
@aeropic

No that's not possible.
The java classes that perform the encoding accept only files not byte arrays as input.

If you'd like a copy of the liblame Eclipse project you're welcome.
If you have some skills in java and JNI you could try to modify it to encode a byte array.

But that's not something i have the skills or time to do :(.

Martin.
 

aeropic

Active Member
Licensed User
Thank you Martin for this fast answer.

I don't have either the skills for Java JNI but I know a good friend of mine who has those skills !
I have asked him whether he accepts to help me and the answer is YES.
So I am glad to accept your proposal and I would of course send back the modified Library to you :) (if it works...)

Many thanks
Alain
 

warwound

Expert
Licensed User
OK.

I've upload the project to here: http://b4a.martinpearman.co.uk/liblame/LAME_B4A.zip

That zip archive contains the LAME_B4A Eclipse project.
This is all you really need - it's everything you need to compile the b4a library.

The zip archive it also contains liblame which is the android library project that the b4a library wraps.

Your mate will probably find it useful to import both projects into Eclipse.

Martin.
 

warwound

Expert
Licensed User
Yes the class com.intervigil.lame.Encoder.java is the class that currently encodes a wav file to mp3.
You can see in it's encode() method that it does internally work with byte arrays and short arrays - but requires a wav input source.

The class net.sourceforge.lame.Lame.java is the JNI class - the class that acts as an interface between java and the underlying C encoder library (the liblame.so file).

Good luck and keep this thread updated with your progress :).

Martin.
 

freedom2000

Well-Known Member
Licensed User
@warwound,

Thank you for sharing this library.
I have tested it with the given example without problem.
But when trying to call it within a service I get the following error

B4X:
Parsing code.                          0.09
Compiling code.                        Error
Error compiling program.
Error description: Cannot access activity object from sub Process_Globals.
Occurred on line: 8
Dim LameEncoder As liblameEncoder
Word: liblameencoder
my code it quite simple...

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Dim LameEncoder As liblameEncoder
    Dim Encoding As Boolean = False
End Sub
Sub Service_Create

    LameEncoder.Initialize("LameEncoder")
End Sub

Sub Service_Start (StartingIntent As Intent)

End Sub

Sub Service_Destroy

End Sub
Sub Encode
    Log("liblame encoder test")
  
...
does the library work into a service ?

I wanted to do this to allow encoding in the background...

Thank you
 

warwound

Expert
Licensed User
@freedom2000

For some reason i made both the encoder and decoder into Activity objects when i created the library.
I'm sure that's not required.
Can you test the attached update and report whether it works as expected?

I've simply removed the annotations that made the encoder and decoder into Activity objects.

Thanks.

Martin.
 
Last edited:

freedom2000

Well-Known Member
Licensed User
@freedom2000

For some reason i made both the encoder and decoder into Activity objects when i created the library.
I'm sure that's not required.
Can you test the attached update and report whether it works as expected?

I've simply removed the annotations that made the encoder and decoder into Activity objects.

Thanks.

Martin.
Thank you for this very fast answer.

I have tried the new lib.
Compilation is OK
But as soon as I launch the App (even without calling the lib) I get this error message (App crash in EncoderWrapper)

B4X:
LogCat connected to: B4A-Bridge: LGE Nexus 5-358240052381506
--------- beginning of /dev/log/main

Installing file.
PackageAdded: package:fr.free.julienGley.SecretRecorder
mp3encode_process_globals (java line: 170)
java.lang.NoClassDefFoundError: uk.co.martinpearman.b4a.liblame.EncoderWrapper
    at fr.free.julienGley.SecretRecorder.mp3encode._process_globals(mp3encode.java:170)
    at fr.free.julienGley.SecretRecorder.main.initializeProcessGlobals(main.java:2087)
    at fr.free.julienGley.SecretRecorder.main.afterFirstLayout(main.java:94)
    at fr.free.julienGley.SecretRecorder.main.access$100(main.java:16)
    at fr.free.julienGley.SecretRecorder.main$WaitForLayout.run(main.java:76)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5017)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
    at dalvik.system.NativeStart.main(Native Method)
Thanks again for your help
 

warwound

Expert
Licensed User
Ooooops!
Wrong java compatibility setting i think.

Try the attached jar file - the xml file from my previous post does not need updating, just the jar file.

Martin.
 

freedom2000

Well-Known Member
Licensed User
Try the attached jar file - the xml file from my previous post does not need updating, just the jar file.

Martin.
Fantastic :)
It works like a charm, I can now encode in the background !

BTW, the decoded file has the limitations that you mentionned (normal !)
But the encoder seems to be very strong, I have encoded 22kHz mono wav file without problem

This "mp3" option will be added very sound into my "JG Secret Recorder"

Thank you again Martin, your lib is Great
 
Last edited:

warwound

Expert
Licensed User
liblame updated to version 1.01

This update adds no new functionality.
It simply fixes a mistake in version 1.00 where the encoder and decoder objects were incorrectly defined as Activity objects.
That prevents them being used in a Service module and also prevents them being declared as Process Global objects in an Activity.

So version 1.01 mean both encoder and decoder are no longer Activity objects.

They can be used in Service modules and also declared as Activity Process Global objects.

Version 1.01 is attached to post #1 in this thread.

Martin.
 

Indy

Active Member
Licensed User
Hi Martin,

I've been using your library in my new project and it's working perfect. However, I've started a new project whereby I convert WAV to MP3. I noticed that in the encoded MP3 file there seem a lot of hissing sound. Would you happen to know why this could be happening? The original WAV file is fine. I also converted the very same WAV file using Audacity using the Windows version of the LAME library and that was fine also. Is there a particular format the WAV file needs to be? i.e 16bit, 44kHz etc..

Thanks
 

warwound

Expert
Licensed User
@Indy

Stereo 16 bit 44100Hz .wav files worked best when i used this library.
Any other format was liable to produce unpredictable results.
 

Neojoy

Member
Licensed User
Hi Martin,

I was working with b4a version 5.3 with targetsdkversion 23 and your lib worked like a charm, when I updated to b4a 8 and sdk 26 my app crash and show this error:
java.lang.UnsatisfiedLinkError: dlopen failed: /data/app/com.myapp-1/lib/arm/liblame.so: has text relocations.

Any ideia?

Thanks
 
Top