将原始PCM转换为FLAC

Convert raw PCM to FLAC?

本文关键字:FLAC 转换 PCM 原始      更新时间:2023-10-16

EDIT:我已经更新了下面的代码,以类似于我所取得的进展。我试着自己写.wav头。代码现在不能正常工作,音频没有被正确地写入文件。该代码还不包含任何将其转换为.flac文件的尝试。


我使用树莓派(Debian Linux)用ALSA库录制音频。录音工作正常,但我需要将输入音频编码为FLAC编解码器。

这就是我迷路的地方。我花了相当多的时间试图弄清楚如何将这些原始数据转换为FLAC,但我不断提出如何将.wav文件转换为.flac文件的示例。

这是当前的(更新)代码,我有记录音频与ALSA(它可能有点粗糙,我仍然拿起c++):

// Use the newer ALSA API
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Riff
{
  char chunkId[4]; // "RIFF" (assuming char is 8 bits)
  int chunkSize; // (assuming int is 32 bits)
  char format[4]; // "WAVE"
};
struct Format
{
  char chunkId[4]; // "fmt "
  int chunkSize;
  short format; // assuming short is 16 bits
  short numChannels;
  int sampleRate;
  int byteRate;
  short align;
  short bitsPerSample;
};
struct Data
{
  char chunkId[4]; // "data"
  int chunkSize; // length of data
  char* data;
};
struct Wave // Actual structure of a PCM WAVE file
{
  Riff riffHeader;
  Format formatHeader;
  Data dataHeader;
};
int main(int argc, char *argv[])
{
        void saveWaveFile(struct Wave *waveFile);
        long loops;
        int rc;
        int size;
        snd_pcm_t *handle;
        snd_pcm_hw_params_t *params;
        unsigned int sampleRate = 44100;
        int dir;
        snd_pcm_uframes_t frames;
        char *buffer;
        char *device = (char*) "plughw:1,0";
        //char *device = (char*) "default";
        printf("Capture device is %sn", device);
        /* Open PCM device for recording (capture). */
        rc = snd_pcm_open(&handle, device, SND_PCM_STREAM_CAPTURE, 0);
        if (rc < 0)
        {
                fprintf(stderr, "Unable to open PCM device: %sn", snd_strerror(rc));
                exit(1);
        }
        /* Allocate a hardware parameters object. */
        snd_pcm_hw_params_alloca(&params);
        /* Fill it in with default values. */
        snd_pcm_hw_params_any(handle, params);
        /* Set the desired hardware parameters. */
        /* Interleaved mode */
        snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
        /* Signed 16-bit little-endian format */
        snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
        /* Two channels (stereo) */
        snd_pcm_hw_params_set_channels(handle, params, 2);
        /* 44100 bits/second sampling rate (CD quality) */
        snd_pcm_hw_params_set_rate_near(handle, params, &sampleRate, &dir);
        /* Set period size to 32 frames. */
        frames = 32;
        snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
        /* Write the parameters to the driver */
        rc = snd_pcm_hw_params(handle, params);
        if (rc < 0)
        {
                fprintf(stderr, "Unable to set HW parameters: %sn", snd_strerror(rc));
                exit(1);
        }
        /* Use a buffer large enough to hold one period */
        snd_pcm_hw_params_get_period_size(params, &frames, &dir);
        size = frames * 4; /* 2 bytes/sample, 2 channels */
        buffer = (char *) malloc(size);
        /* We want to loop for 5 seconds */
        snd_pcm_hw_params_get_period_time(params, &sampleRate, &dir);
        loops = 5000000 / sampleRate;
        while (loops > 0)
        {
                loops--;
                rc = snd_pcm_readi(handle, buffer, frames);
                if (rc == -EPIPE)
                {
                        /* EPIPE means overrun */
                        fprintf(stderr, "Overrun occurred.n");
                        snd_pcm_prepare(handle);
                } else if (rc < 0)
                {
                        fprintf(stderr, "Error from read: %sn", snd_strerror(rc));
                } else if (rc != (int)frames)
                {
                        fprintf(stderr, "Short read, read %d frames.n", rc);
                }
                if (rc != size) fprintf(stderr, "Short write: wrote %d bytes.n", rc);
        }
        Wave wave;
        strcpy(wave.riffHeader.chunkId, "RIFF");
        wave.riffHeader.chunkSize = 36 + size;
        strcpy(wave.riffHeader.format, "WAVE");
        strcpy(wave.formatHeader.chunkId, "fmt");
        wave.formatHeader.chunkSize = 16;
        wave.formatHeader.format = 1; // PCM, other value indicates compression
        wave.formatHeader.numChannels = 2; // Stereo
        wave.formatHeader.sampleRate = sampleRate;
        wave.formatHeader.byteRate = sampleRate * 2 * 2;
        wave.formatHeader.align = 2 * 2;
        wave.formatHeader.bitsPerSample = 16;
        strcpy(wave.dataHeader.chunkId, "data");
        wave.dataHeader.chunkSize = size;
        wave.dataHeader.data = buffer;
        saveWaveFile(&wave);
        snd_pcm_drain(handle);
        snd_pcm_close(handle);
        free(buffer);
        return 0;
}
void saveWaveFile(struct Wave *waveFile)
{
        FILE *file = fopen("test.wav", "wb");
        size_t written;
        if (file == NULL)
        {
                fprintf(stderr, "Cannot open file for writing.n");
                exit(1);
        }
        written = fwrite(waveFile, sizeof waveFile[0], 1, file);
        fclose(file);
        if (written < 1);
        {
                fprintf(stderr, "Writing to file failed, error %d.n", written);
                exit(1);
        }
}

我该如何将PCM数据转换为FLAC并将其保存到磁盘以供以后使用?我已经下载了libflac-dev,只需要一个例子去。


我现在做的方式:

./capture > test.raw     // or   ./capture > test.flac

应该是这样的(程序为我做了一切):

./capture

如果我理解FLAC::Encoder::File文档,你可以做一些像

#include <FLAC++/encoder.h>
FLAC::Encoder::File encoder;
encoder.init("outfile.flac");
encoder.process(buffer, samples);
encoder.finish();

其中buffer是一个32位整数指针数组(大小为samples)。

不幸的是,我对音频编码几乎一无所知,所以我不能说任何其他选择。好运!

请参考以下代码:

FLAC编码器测试代码

本例使用wav文件作为输入,然后将其编码为FLAC。

据我所知,b/w WAV文件和你的RAW数据没有大的区别,我认为你可以修改这段代码直接读取"缓冲区"并转换它。你已经有了所有相关的信息(通道/比特率等),所以删除WAV头读取代码应该不是什么大问题。

请注意:这是他们git仓库中Flac Encoder示例的修改版本。

它包括一些关于如何将其更改为OP要求的注释和提示,整个源代码将有点长。

请注意,这是C API,它往往比c++ API更复杂一些。但是,一旦你了解了这个概念,在两者之间转换就相当容易了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "share/compat.h"  
#include "FLAC/metadata.h"
#include "FLAC/stream_encoder.h"
/* this call back is what tells your program the progress that the encoder has made */
static void progress_callback(const FLAC__StreamEncoder *encoder, FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate, void *client_data);
#define READSIZE 1024
static unsigned total_samples = 0; /* can use a 32-bit number due to WAVE size limitations */
/* buffer is where we record to, in your case what ALSA writes to */
/* Note the calculation here to take the total bytes that the buffer takes */
static FLAC__byte buffer[READSIZE/*samples*/ * 2/*bytes_per_sample*/ * 2/*channels*/]; 
/* pcm is input to FLAC encoder */
/* the PCM data should be here, bps is 4 here...but we are allocating ints! */
static FLAC__int32 pcm[READSIZE/*samples*/ * 2/*channels*/];
int main(int argc, char *argv[]) 
{
FLAC__bool ok = true;
FLAC__StreamEncoder *encoder = 0;
FLAC__StreamEncoderInitStatus init_status;
FLAC__StreamMetadata *metadata[2];
FLAC__StreamMetadata_VorbisComment_Entry entry;
FILE *fin;
unsigned sample_rate = 0;
unsigned channels = 0;
unsigned bps = 0;

if((fin = fopen(argv[1], "rb")) == NULL) {
    fprintf(stderr, "ERROR: opening %s for outputn", argv[1]);
    return 1;
}
/* set sample rate, bps, total samples to encode here, these are dummy values */
sample_rate = 44100;
channels = 2;
bps = 16;
total_samples = 5000;
/* allocate the encoder */
if((encoder = FLAC__stream_encoder_new()) == NULL) {
    fprintf(stderr, "ERROR: allocating encodern");
    fclose(fin);
    return 1;
}
ok &= FLAC__stream_encoder_set_verify(encoder, true);
ok &= FLAC__stream_encoder_set_compression_level(encoder, 5);
ok &= FLAC__stream_encoder_set_channels(encoder, channels);
ok &= FLAC__stream_encoder_set_bits_per_sample(encoder, bps);
ok &= FLAC__stream_encoder_set_sample_rate(encoder, sample_rate);
ok &= FLAC__stream_encoder_set_total_samples_estimate(encoder, total_samples);
/* sample adds meta data here I've removed it for clarity */
/* initialize encoder */
if(ok) {
    /* client data is whats the progress_callback is called with, any objects you need to update on callback can be passed thru this pointer */
    init_status = FLAC__stream_encoder_init_file(encoder, argv[2], progress_callback, /*client_data=*/NULL);
    if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
        fprintf(stderr, "ERROR: initializing encoder: %sn", FLAC__StreamEncoderInitStatusString[init_status]);
        ok = false;
    }
}
/* read blocks of samples from WAVE file and feed to encoder */
if(ok) {
    size_t left = (size_t)total_samples;
    while(ok && left) {
    /* record using ALSA and set SAMPLES_IN_BUFFER */
    /* convert the packed little-endian 16-bit PCM samples from WAVE into an interleaved FLAC__int32 buffer for libFLAC */
    /* why? because bps=2 means that we are dealing with short int(16 bit) samples these are usually signed if you do not explicitly say that they are unsigned */
            size_t i;
            for(i = 0; i < SAMPLES_IN_BUFFER*channels; i++) {
                                    /* THIS. this isn't the only way to convert between formats, I do not condone this because at first the glance the code seems like it's processing two channels here, but it's not it's just copying 16bit data to an int array, I prefer to use proper type casting, none the less this works so... */
                pcm[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)buffer[2*i+1] << 8) | (FLAC__int16)buffer[2*i]);
            }
            /* feed samples to encoder */
            ok = FLAC__stream_encoder_process_interleaved(encoder, pcm, SAMPLES_IN_BUFFER);
        left-=SAMPLES_IN_BUFFER;
    }
}
ok &= FLAC__stream_encoder_finish(encoder);
fprintf(stderr, "encoding: %sn", ok? "succeeded" : "FAILED");
fprintf(stderr, "   state: %sn", FLAC__StreamEncoderStateString[FLAC__stream_encoder_get_state(encoder)]);

FLAC__stream_encoder_delete(encoder);
fclose(fin);
return 0;
}
/* the updates from FLAC's encoder system comes here */
void progress_callback(const FLAC__StreamEncoder *encoder, FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate, void *client_data)
{
(void)encoder, (void)client_data;
fprintf(stderr, "wrote %" PRIu64 " bytes, %" PRIu64 "/%u samples, %u/%u framesn", bytes_written, samples_written, total_samples, frames_written, total_frames_estimate);
}