Encoding audio files with Lame
PUBLISHED
Introduction
This article will present you how to encode sounds into mp3 file using the library named Lame. The library is distributed under LGPL license. It is considered to offer the best quality of mp3 encoding.
The topic will be presented on the example code of a voice recorder application. First of all it will be described how to import the library to Tizen native project. The source of sound which will be encoded is microphone, so the article will present you how to use the audio input programming interface. After that it will be presented how to use internal storage to save the encoded file. At the end it will be described how to play the resulting mp3 file.
The article was created for developers who have basic knowledge of the C programming language and the Tizen core application life cycle. The graphical interface of the sample application was maximally simplified, because designing user interfaces is not the aim of this article.
Building Lame library for Tizen
Lame is a library written in C programming language. In this part I will present you how to build it as a static library for Tizen in Tizen IDE 2.3. Built library project was attached to this article, but knowing how to import the library probably will be useful to port next versions of Lame or any other library written in C.
First of all you should download the code of the library from this website. Open the IDE and create a new Static Library project in that way: click File->New->Tizen Native Project and in the pop-up window chose Library->Static Library. Then type a name of the library and click finish.
After unpacking the library, copy all files with extension *.h from folder /libmp3lame, lame.h from /include and config.h (which was attached to the article) into folder inc in your library project. Remember about lame_intrin.h from /libmp3lame/vector and copy it into /inc/vector. Copy all the files with *.c extension from /libmp3lame into /src folder.
Then define the new preprocessor symbol HAVE_CONFIG_H. Click on the project with right button and chose Properties from contextual menu. The place where you can add the variable is marked on the screen below:
Figure 1 Addition a HAVE_CONFIG_H preprocessor symbol.
After that you can build your project. The resulting library with extension *.a is located in folder Debug (or Release) in your project folder. It is important to build the library for correct architecture, because the emulator works on x86, but the device uses ARM architecture. On the figure below was marked where you can change the architecture of the project:
Figure 2 Changing build architecture for library project.
Using Lame library
For using the library, you must copy built .a file into folder /lib in your project and copy the lame.h file into /inc. After that you should add the library as in the screen below.
Figure 3 Adding libraries to the project.
At this moment if you want to use any function from Lame library you should include only the header file to the source code.
Using audio input
Using the recorder requires the addition of the following permission to your tizen-manifest.xml file:
<privileges> <privilege> http://tizen.org/privilege/recorder</privilege> </privileges>
The second step to use the audio input is including a following header file into the source code:
#include <audio_io.h>
After that if you want to get audio from input stream, you should register callback function which has following form:
void _audio_io_stream_read_cb(audio_in_h handle, size_t nbytes, void *userdata) { […] }
where, nbytes is number of bytes that you received, *userdata is pointer to data which you can forwarded to the function. Using the handler you can receive audio data in the following way:
// Retrieve buffer pointer from audio in buffer if(audio_in_peek(handle, &buffer, &nbytes) != AUDIO_IO_ERROR_NONE) { return; }
where, the buffer is a pointer to allocated nbytes of memory. After processing the data you should use the following function to remove data from stream buffer.
// Remove audio in data from actual stream buffer audio_in_drop(handle);
Registration of the callback you can make in the following manner:
bool init_audio_IO(void) { audio_io_error_e ret; /* initialize audio input */ ret = audio_in_create(SAMPLE_RATE, AUDIO_CHANNEL_STEREO, AUDIO_SAMPLE_TYPE_S16_LE, &input); if(ret != AUDIO_IO_ERROR_NONE) { LOGE("Filed to initialize audio input"); return false; } // register callback if(audio_in_set_stream_cb(input, _audio_io_stream_read_cb, NULL)!= AUDIO_IO_ERROR_NONE) { LOGE("Filed to register callback"); return false; } if(audio_in_prepare(input) != AUDIO_IO_ERROR_NONE) { LOGE("Filed to prepare audio input"); audio_in_destroy(input); return false; } return true; }
The audio_in_create function initializes the audio input. Sample rate ought to be between 8000 and 48000 Hz. In sample app we use 44100 samples per seconds. Second argument set the number of channels and the third defines number of bits per sample. We can choose between 8 (AUDIO_SAMPLE_TYPE_8U) and 16 (AUDIO_SAMPLE_TYPE_S16_LE) bits per sample. Last argument is a handler to the audio input. The next function audio_in_set_stream_cb register the callback function. Last argument is a pointer to data which we want to forward to the callback function. In the sample code it wasn’t use. The last function that we ought to use is audio_in_prepare. After that the callback function will be called. If we want to stop using the audio input in the callback, we ought to use audio_in_unprepare function. And if we will never use the handler, we should free the allocated memory by the handler using audio_in_destroy.
int close_audio_IO(){ if(audio_in_unprepare(input) != AUDIO_IO_ERROR_NONE) { return false; } if(audio_in_destroy(input) != AUDIO_IO_ERROR_NONE) { return false; } return true; }
Using internal storage
In the sample application the result *.mp3 file is saved in the internal storage in the proper directory for sounds in the Tizen operating system. To save anything in the media storage you should add privilege to your project by adding the following lines to tizen-manifest.xml file:
<privileges> <privilege> http://tizen.org/privilege/mediastorage</privilege> </privileges>
First of all we should get the file path. The file path in the sample application was obtained in the following manner:
#include <storage.h> #define FILE_NAME “recorder.mp3” static char file_path[PATH_MAX]; […] bool set_file_path(void) { int error; char *path; error = storage_get_directory(internal_storage_id, STORAGE_DIRECTORY_SOUNDS, &path); if(error != STORAGE_ERROR_NONE){ return false; } snprintf(file_path, sizeof(file_path), "%s/"FILE_NAME, path); free(path); return true; }
The storage_get_directory function get in first argument a storage id, the code below shows you how to find the id. To use the function you should include storage.h header file.
static bool _storage_cb(int storage_id, storage_type_e type, storage_state_e state, const char *path, void *user_data) { if (type == STORAGE_TYPE_INTERNAL) { internal_storage_id = storage_id; return false; } return true; } void get_storage(void) { int error = storage_foreach_device_supported(_storage_cb, NULL); if(error != STORAGE_ERROR_NONE) { LOGE("FILED TO GET INTERNAL STORAGE"); } }
If you already have a valid file path, to manipulate content of the file you can use function from standard IO library.
Using Lame encoder
The code below present you sample function used to initialize lame encoder. Sample rate and number of channels ought to be the same as in your audio input. The third parameter is the size of the resulting song after compression expressed in kilobits per each seconds of its duration. This parameter has the most important impact on quality after compression. The last parameter must be in the range from 0 to 9 and has also an effect on the quality, because it determined the algorithm of compression. If the value of the parameter is 0, the quality will be the best but the decompression will be most expensive for your computation unit. In the example application the variable bit rate was set off, because if the algorithm used VBR the build in player can’t proper recognize the duration of songs. If it is enabled, the resulting song will take up less space in the memory without loss of quality.
int init_encoder(int samplePerSecond, int channels, int bitrate, int quality){ gfp = lame_init(); if(gfp){ lame_set_in_samplerate(gfp, samplePerSecond); lame_set_num_channels(gfp, channels); lame_set_num_samples(gfp, NUM_SAMPLES); lame_set_brate(gfp, bitrate); lame_set_quality(gfp, quality); lame_set_VBR(gfp, vbr_off); if(lame_init_params(gfp) < 0) { LOGE("FAILED TO INITIALIZE LAME!!"); return 0; } return 1; } return 0; }
To encode the pcm signal it was used the lame_encode_buffer_interleaved, because signal come from audio input for right and left channel are interleaved and the resulting data is returned as one stream. If you encode last fragment of your audio signal, you should use lame_encode_flush function. If you want to know more about lame library I recommend you to read comments from the code, because the descriptions under each of function are very well.
Playing the encoded audio files
Tizen native API provides you an opportunity to play mp3 files. In this part it will be explained how to use the player. To use player functions you should attached player.h header file into your project. After that you can initialize the player in this manner:
#include <player.h> typedef struct appdata { […] player_h player; […] } appdata_s; […] void init_base_player(appdata_s *ad) { int error_code = 0; error_code = player_create(&ad->player); if (error_code != PLAYER_ERROR_NONE) dlog_print(DLOG_ERROR, LOG_TAG, "failed to create"); }
Before play an audio resource you should make 3 steps. First of it is setup the proper file path. After that you must prepare the player with the settings. The media content of the file will be loaded. The third step is rewinding the track to proper position (in the sample app it is the beginning), because if it was played before it will start from the same point where it had been stopped playing. In Tizen 2.3 the rewinding is realized asynchronously, so you must register callback using player_set_play_position function. In sample application it was realized in the following manner:
void _seek_completed_cb(void *data) { appdata_s *ad = data; if(player_start(ad->player) != PLAYER_ERROR_NONE) { LOGE("FILED TO PLAY"); } ecore_timer_add (TIMER_REFRESH_FREQUENCY, timer_cb, ad); } void play_audio_file(appdata_s *ad) { if(player_set_uri(ad->player, get_file_path()) != PLAYER_ERROR_NONE) { LOGE("FILED TO SET FILE URI"); } if(player_prepare(ad->player) != PLAYER_ERROR_NONE) { LOGE("FILED TO PREPARE PLAYER"); } player_set_play_position(ad->player, 0, true, _seek_completed_cb, ad); }
After using the player you should free the player resources using player_destroy function. The code below show you example how to use it:
void release_base_player(appdata_s *ad) { if(player_unprepare(ad->player) == PLAYER_ERROR_NONE) { player_destroy(ad->player); } }
Summary
In this article you learned how to port the lame library, import it into Tizen native application and use to encode raw pcm sound data into mp3 format. After that it was shown how to save the result file into local storage. Moreover you have learned how to get pcm sample from audio input and play audio file from local memory. All these elements were used in sample app which is simple voice recorder.
Attachments
- Lame - example of ported library
- usingLame - sample aplication using Lame library
- lib_emulator_X86 - built static library for architectures ARM and configurations (debug, release).
- lib_device_ARM - built static library for architectures X86 and configurations (debug, release).