【问题标题】:How to convert PCM 8kHz 8bit unsigned to mp3 in C如何在 C 中将 PCM 8kHz 8bit unsigned 转换为 mp3
【发布时间】:2015-10-29 00:39:29
【问题描述】:

我完全不了解与编程音频处理相关的主题,但我想将 PCM 音频数据转换为 MP3。

我的音频是PCM格式,频率为8kHz,位宽为8,字节类型为无符号,声道为单声道。

我正在使用 liblamemp3,通过执行以下命令行,我找到了 lame.exe 前端的方法:

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

结果是一个我听得很清楚的.mp3。 (经过一些尝试)。

问题是我想即时完成,所以我尝试编写一些源代码来完成它而不调用前端。

我的源代码是:

#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;
}

我从一个示例中获取它,并尝试应用我需要的设置。但是生成的 .mp3 听起来像一个反应堆,听起来不太好。所以我错过了一些/很多代码。

我想做,有谁能帮忙吗?

提前致谢。

【问题讨论】:

  • 也许你的字节序是倒序的?
  • 哦,谢谢。所以我必须从 PCM 缓冲区的末尾开始?
  • 不不不不。每个 PCM 样本中的字节顺序。一些系统(x86、ARM)是小端,而其他系统(PPC、网络等)是大端是一个常见的问题。您正在阅读的 PCM 数据可能是大端,而图书馆期待小端。或者,也许您实际上不是在读取 PCM 数据,而是在读取一个 wav 文件头,加上 PCM 等。
  • 好的。如果我没记错的话,每个 PCM 样本是 8 个字节(4 个短裤)。我是否应该将每个短帧的顺序和 8 字节大小的传递帧交换到 lame_encode_buffer_interleaved(),但之前已交换?抱歉,我对音频一无所知...
  • .pcm 文件也没有标题。这只是 RAW PCM 音频数据。

标签: c mono mp3 pcm lame


【解决方案1】:
  1. 每个样本只有 1 个字节,因此您应该读取 1 个字节,而不是读取 sizeof(short) 字节。
  2. 您必须将无符号的 8 位 PCM 样本转换为有符号的 16 位 PCM 样本 (reference)。
  3. 由于输入是单声道,所以使用lame_encode_buffer()而不是lame_encode_buffer_interleaved(),并将buffer_r参数设置为NULLreference)。

这是您代码的修改后的工作版本:

#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;
}

上述代码一次从输入文件中读取 1 个字节,效率不高。这是一次读取多个字节的方法(我还清理了一些代码并删除了一些不必要的函数)。此代码中没有错误检查,因此请确保添加对所有 LAME 库函数的返回值的检查。

#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;
}

【讨论】:

  • 非常感谢蒂姆!真的行。我只是在 while(1) 中添加了一个条件,只是:while (n_bytes_read > 0);我会照顾 LAME 功能。再次感谢!我正在读取从无符号 8 位到有符号 16 位的转换。:)))
  • 我很高兴它对你有用!另外,感谢您指出循环的问题,现在已修复。
  • 您好!现在我已经完成了这项工作,我想知道是否可以使用 LAME 恢复该过程并获得开始的原始 pcm。我正在查看 hip_decode() 函数,但至少在命令行中我无法做到这一点。 (可能不支持)。我试过这个命令行:./lame --decode -t file.mp3 file.pcm。提前致谢! ^^
  • 你应该在一个单独的问题中提出这个问题。
  • 谢谢,如果我需要帮助,我会这样做,但我找到了方法。只是 hip_decode() 和之前读取缓冲区。但我正在获得 16 位 PCM,这对于 pourpose 来说还不错。无论如何,我有你为转换所做的技巧,我想我可以做相反的事情。非常感谢蒂姆,你是男人^^
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-04
  • 2020-10-11
  • 2013-03-31
相关资源
最近更新 更多