3
votes

I am a completely noob in topics related to audio handling with programming, but I want to convert PCM audio data to MP3.

My audio is in PCM format, the frequency is 8kHz, the bitwidth is 8 and the kind of byte is unsigned, and channel is mono.

I am using liblamemp3, and I found the way of doing it by the lame.exe frontend, by executing this command line:

$ ./lame -r -s 8 --bitwidth 8 --unsigned -m m ../../../../voice8K16bitmono.pcm output.mp3

The result is an .mp3 that I can hear well. (After some tries).

The problem is that I want to do it on the fly, so I am trying to code some source to do it without invoking the frontend.

My source code is:

#include <stdio.h>
#include <lame/lame.h>

int main(int argc, char *argv[])
{
    int read, write;

    FILE *pcm = fopen(argv[1], "rb");
    FILE *mp3 = fopen("file.mp3", "wb");

    const int PCM_SIZE = 8192;
    const int MP3_SIZE = 8192;

    unsigned short pcm_buffer;
    unsigned short pcm_buffer_le;
    unsigned char mp3_buffer[MP3_SIZE];

    lame_t lame = lame_init();
    lame_set_num_samples(lame, 8000);
    lame_set_in_samplerate(lame, 8000);
    lame_set_out_samplerate(lame, 8000);
    lame_set_num_channels(lame, 1);
    lame_set_mode(lame, 3);
    lame_init_params(lame);
    lame_print_config(lame);
    //framesize is bits / channels = 8.
    do {
        read = fread(&pcm_buffer, sizeof(short), 1, pcm);
    pcm_buffer = (pcm_buffer>>8) | (pcm_buffer<<8);
    if (read == 0) {
            write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
            fwrite(mp3_buffer, sizeof(char), write, mp3);
        break;
    }
    //pcm_buffer_le[0] = (pcm_buffer[0]>>8) | (pcm_buffer[0]<<8);
        write = lame_encode_buffer_interleaved(lame, &pcm_buffer, sizeof(short), mp3_buffer, MP3_SIZE);
        fwrite(mp3_buffer, sizeof(char), write, mp3);
    } while (1);

    lame_close(lame);
    fclose(mp3);
    fclose(pcm);

    return 0;
}

I took it from an example, and tried to apply the settings I need. But the resulting .mp3 sounds like a reactor and it doesn't sound well. So I am missing some/lot of code.

I want to do it, so, can anybody help?

Thanks in advance.

1
maybe your byte order is backwards?Russ Schultz
Oh, thanks. So I must start from the end of the PCM buffer?Puffy
no no no no. The byte order within each PCM sample. It's a common problem that some systems (x86, ARM) are little endian, and other systems (PPC, network, etc) are big endian. The PCM data you're reading might be big endian, and the library is expecting little endian. Or maybe you're not actually reading PCM data, but a wav file header, plus PCM, etc.Russ Schultz
Ok. If I am not wrong, each PCM sample is 8 bytes (4 shorts). Should I swap the order of each short and pass frames of 8 bytes sized, to the lame_encode_buffer_interleaved(), but previously swapped? Sorry but I don't know anything about audio...Puffy
also the .pcm file hasn't header. It's just RAW PCM audio data.Puffy

1 Answers

1
votes
  1. Each sample is only 1 byte, so instead of reading in sizeof(short) bytes, you should read in 1 byte.
  2. You have to convert the unsigned 8-bit PCM samples into signed 16-bit PCM samples (reference).
  3. Since the input is mono, use lame_encode_buffer() instead of lame_encode_buffer_interleaved(), and set the buffer_r parameter to NULL (reference).

Here is a modified working version of your code:

#include <stdio.h>
#include <lame/lame.h>

int main(int argc, char *argv[])
{
    int read, write;

    FILE *pcm = fopen(argv[1], "rb");
    FILE *mp3 = fopen("file.mp3", "wb");

    const int MP3_SIZE = 8192;

    unsigned char pcm_buffer_c;
    short pcm_buffer;
    unsigned char mp3_buffer[MP3_SIZE];

    lame_t lame = lame_init();
    lame_set_num_samples(lame, 8000);
    lame_set_in_samplerate(lame, 8000);
    lame_set_out_samplerate(lame, 8000);
    lame_set_num_channels(lame, 1);
    lame_set_mode(lame, 3);
    lame_init_params(lame);
    lame_print_config(lame);
    //framesize is bits / channels = 8.
    do {
        read = fread(&pcm_buffer_c, sizeof(char), 1, pcm);
        pcm_buffer = (short)(pcm_buffer_c - 0x80) << 8;
        if (read == 0) {
            write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
            fwrite(mp3_buffer, sizeof(char), write, mp3);
            break;
        }
        write = lame_encode_buffer(lame, &pcm_buffer, NULL, read, mp3_buffer, MP3_SIZE);
        fwrite(mp3_buffer, sizeof(char), write, mp3);
    } while (1);

    lame_close(lame);
    fclose(mp3);
    fclose(pcm);

    return 0;
}

The above code reads in 1 byte at a time from the input file, which isn't very efficient. Here's how you would read multiple bytes at a time (I also cleaned up some of your code and removed some unnecessary functions). There is no error checking in this code, so make sure you add checks for the return values of all the LAME library functions.

#include <stdio.h>
#include <lame/lame.h>

#define PCM_BUF_SIZE 1024
#define MP3_SIZE 8192

int main(int argc, char *argv[])
{
    FILE *pcm = fopen(argv[1], "rb");
    FILE *mp3 = fopen("file.mp3", "wb");

    int n_bytes_read;
    int n_bytes_write;
    int i;

    short pcm_buffer_s[PCM_BUF_SIZE];
    unsigned char pcm_buffer[PCM_BUF_SIZE];
    unsigned char mp3_buffer[MP3_SIZE];

    lame_t lame = lame_init();
    lame_set_in_samplerate(lame, 8000);
    lame_set_num_channels(lame, 1);
    lame_set_mode(lame, 3);
    lame_init_params(lame);
    lame_print_config(lame);
    do {
        n_bytes_read = fread(pcm_buffer, sizeof(char), PCM_BUF_SIZE, pcm);
        for (i = 0; i < n_bytes_read; i++) {
            pcm_buffer_s[i] = (short)(pcm_buffer[i] - 0x80) << 8;
        }
        if (n_bytes_read == 0) {
            n_bytes_write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
        } else {
            n_bytes_write = lame_encode_buffer(lame, pcm_buffer_s, NULL,
                    n_bytes_read, mp3_buffer, MP3_SIZE);
        }
        fwrite(mp3_buffer, sizeof(char), n_bytes_write, mp3);
    } while (n_bytes_read > 0);

    lame_close(lame);
    fclose(mp3);
    fclose(pcm);

    return 0;
}