Pregunta

EDIT: I've updated the code below to resemble the progress I have made. I'm trying to write the .wav header myself. The code does not work properly as of now, the audio is not being written to the file properly. The code does not contain any attempts to convert it to a .flac file yet.


I am using a Raspberry Pi (Debian Linux) to record audio with the ALSA library. The recording works fine, but I need to encode the input audio into the FLAC codec.

This is where I get lost. I have spent a considerable amount of time trying to figure out how to convert this raw data into FLAC, but I keep coming up with examples of how to convert .wav files into .flac files.

Here is the current (updated) code I have for recording audio with ALSA (it may be a bit rough, I'm still picking up 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 %s\n", 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: %s\n", 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: %s\n", 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: %s\n", 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);
        }
}

How would I go about converting the PCM data into the FLAC and save it to disk for later use? I have downloaded libflac-dev already and just need an example to go off of.


The way I am doing it right now:

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

The way it should be (program does everything for me):

./capture
¿Fue útil?

Solución 2

Please refer to the below code :

FLAC Encoder Test Code

This example is using a wav file as an input and then encodes it into FLAC.

As I understand, there is no major difference b/w WAV file and your RAW data, I think you can modify this code to directly read the "buffer" and convert it. You already have all the related information (Channel/Bitrate etc) so it should not be much of a problem to remove the WAV header reading code.

Otros consejos

If I understand the FLAC::Encoder::File documentation, you can do something like

#include <FLAC++/encoder.h>

FLAC::Encoder::File encoder;
encoder.init("outfile.flac");
encoder.process(buffer, samples);
encoder.finish();

where buffer is an array (of size samples) of 32-bit integer pointers.

Unfortunately, I know next to nothing about audio encoding so I can't speak for any other options. Good luck!

Please note: this is a modified version of the Flac Encoder sample from their git repo.

It includes some comments and hints on how to change it to OP's requirements, entire source for this will be a little bit long.

And do note that this is the C API, which tends to be a bit more complex than the C++ one. But it is fairly easy to convert between the two once you get the idea.

#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 output\n", 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 encoder\n");
    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: %s\n", 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: %s\n", ok? "succeeded" : "FAILED");
fprintf(stderr, "   state: %s\n", 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 frames\n", bytes_written, samples_written, total_samples, frames_written, total_frames_estimate);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top