2

In my previous question here, I managed to get a working .wav file as the output. However, when I throw this .wav file into my encoder (tested with other .wav files and works perfectly), my encoder throws back an error back at me right before it finishes.

Here is my output:

$ ./capture 2
Capture device is plughw:1,0    
Finished writing to /tmp/filevXDDX6.wav
Starting encode to /tmp/filevXDDX6.flac
Wrote 3641 bytes, 4096/88200 samples, 2/22 frames
Wrote 6132 bytes, 8192/88200 samples, 2/22 frames
Wrote 8748 bytes, 12288/88200 samples, 3/22 frames
Wrote 11253 bytes, 16384/88200 samples, 4/22 frames
Wrote 13697 bytes, 20480/88200 samples, 5/22 frames
Wrote 16222 bytes, 24576/88200 samples, 6/22 frames
Wrote 18811 bytes, 28672/88200 samples, 7/22 frames
Wrote 21900 bytes, 32768/88200 samples, 8/22 frames
Wrote 24681 bytes, 36864/88200 samples, 9/22 frames
Wrote 27408 bytes, 40960/88200 samples, 10/22 frames
Wrote 30494 bytes, 45056/88200 samples, 11/22 frames
Wrote 34107 bytes, 49152/88200 samples, 12/22 frames
Wrote 37447 bytes, 53248/88200 samples, 13/22 frames
Wrote 40719 bytes, 57344/88200 samples, 14/22 frames
Wrote 45257 bytes, 61440/88200 samples, 15/22 frames
Wrote 48735 bytes, 65536/88200 samples, 16/22 frames
Wrote 52842 bytes, 69632/88200 samples, 17/22 frames
Wrote 56529 bytes, 73728/88200 samples, 18/22 frames
Wrote 60185 bytes, 77824/88200 samples, 19/22 frames
Wrote 63906 bytes, 81920/88200 samples, 20/22 frames
ERROR: reading from WAVE file
Wrote 67687 bytes, 86016/88200 samples, 21/22 frames
Encoding: FAILED
   State: FLAC__STREAM_ENCODER_UNINITIALIZED

I'm not sure why it's cutting off early, though I'm pretty sure it has to do with how I'm trying to record my own .wav file (since my encoder worked fine with other files).

Here is my code (sorry it's a bit long, I cut down as much as possible):

// Compile with "g++ test.ccp -o test -lasound"

// Use the newer ALSA API
#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

struct WaveHeader
{
        char RIFF_marker[4];
        uint32_t file_size;
        char filetype_header[4];
        char format_marker[4];
        uint32_t data_header_length;
        uint16_t format_type;
        uint16_t number_of_channels;
        uint32_t sample_rate;
        uint32_t bytes_per_second;
        uint16_t bytes_per_frame;
        uint16_t bits_per_sample;
};

struct WaveHeader *genericWAVHeader(uint32_t sample_rate, uint16_t bit_depth, uint16_t channels)
{
    struct WaveHeader *hdr;
    hdr = (WaveHeader*) malloc(sizeof(*hdr));
    if (!hdr)
        return NULL;

    memcpy(&hdr->RIFF_marker, "RIFF", 4);
    memcpy(&hdr->filetype_header, "WAVE", 4);
    memcpy(&hdr->format_marker, "fmt ", 4);
    hdr->data_header_length = 16;
    hdr->format_type = 1;
    hdr->number_of_channels = channels;
    hdr->sample_rate = sample_rate;
    hdr->bytes_per_second = sample_rate * channels * bit_depth / 8;
    hdr->bytes_per_frame = channels * bit_depth / 8;
    hdr->bits_per_sample = bit_depth;

    return hdr;
}

int writeWAVHeader(int fd, struct WaveHeader *hdr)
{
    if (!hdr)
        return -1;

    write(fd, &hdr->RIFF_marker, 4);
    write(fd, &hdr->file_size, 4);
    write(fd, &hdr->filetype_header, 4);
    write(fd, &hdr->format_marker, 4);
    write(fd, &hdr->data_header_length, 4);
    write(fd, &hdr->format_type, 2);
    write(fd, &hdr->number_of_channels, 2);
    write(fd, &hdr->sample_rate, 4);
    write(fd, &hdr->bytes_per_second, 4);
    write(fd, &hdr->bytes_per_frame, 2);
    write(fd, &hdr->bits_per_sample, 2);
    write(fd, "data", 4);

    uint32_t data_size = hdr->file_size + 8 - 44;
    write(fd, &data_size, 4);

    return 0;
}

int recordWAV(const char *fileName, struct WaveHeader *hdr, uint32_t duration)
{
    int err;
    int size;
    snd_pcm_t *handle;
    snd_pcm_hw_params_t *params;
    unsigned int sampleRate = hdr->sample_rate;
    int dir;
    snd_pcm_uframes_t frames = 32;
    char *device = (char*) "plughw:1,0";
    char *buffer;
    int filedesc;

    printf("Capture device is %s\n", device);

    /* Open PCM device for recording (capture). */
    err = snd_pcm_open(&handle, device, SND_PCM_STREAM_CAPTURE, 0);
    if (err)
    {
        fprintf(stderr, "Unable to open PCM device: %s\n", snd_strerror(err));
        return err;
    }

    /* 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 */
    err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (err)
    {
        fprintf(stderr, "Error setting interleaved mode: %s\n", snd_strerror(err));
        snd_pcm_close(handle);
        return err;
    }
    /* Signed 16-bit little-endian format */
    if (hdr->bits_per_sample == 16) err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
    else err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_U8);
    if (err)
    {
        fprintf(stderr, "Error setting format: %s\n", snd_strerror(err));
        snd_pcm_close(handle);
        return err;
    }
    /* Two channels (stereo) */
    err = snd_pcm_hw_params_set_channels(handle, params, hdr->number_of_channels);
    if (err)
    {
        fprintf(stderr, "Error setting channels: %s\n", snd_strerror(err));
        snd_pcm_close(handle);
        return err;
    }
    /* 44100 bits/second sampling rate (CD quality) */
    sampleRate = hdr->sample_rate;
    err = snd_pcm_hw_params_set_rate_near(handle, params, &sampleRate, &dir);
    if (err)
    {
        fprintf(stderr, "Error setting sampling rate (%d): %s\n", sampleRate, snd_strerror(err));
        snd_pcm_close(handle);
        return err;
    }
    hdr->sample_rate = sampleRate;
    /* Set period size*/
    err = snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
    if (err)
    {
        fprintf(stderr, "Error setting period size: %s\n", snd_strerror(err));
        snd_pcm_close(handle);
        return err;
    }
    /* Write the parameters to the driver */
    err = snd_pcm_hw_params(handle, params);
    if (err < 0)
    {
        fprintf(stderr, "Unable to set HW parameters: %s\n", snd_strerror(err));
        snd_pcm_close(handle);
        return err;
    }

    /* Use a buffer large enough to hold one period */
    err = snd_pcm_hw_params_get_period_size(params, &frames, &dir);
    if (err)
    {
        fprintf(stderr, "Error retrieving period size: %s\n", snd_strerror(err));
        snd_pcm_close(handle);
        return err;
    }

    size = frames * hdr->bits_per_sample / 8 * hdr->number_of_channels; /* 2 bytes/sample, 2 channels */
    buffer = (char *) malloc(size);
    if (!buffer)
    {
        fprintf(stdout, "Buffer error.\n");
        snd_pcm_close(handle);
        return -1;
    }

    err = snd_pcm_hw_params_get_period_time(params, &sampleRate, &dir);
    if (err)
    {
        fprintf(stderr, "Error retrieving period time: %s\n", snd_strerror(err));
        snd_pcm_close(handle);
        free(buffer);
        return err;
    }

    uint32_t pcm_data_size = hdr->sample_rate * hdr->bytes_per_frame * duration / 1000;
    hdr->file_size = pcm_data_size + 44 - 8;

    filedesc = open(fileName, O_WRONLY | O_CREAT, 0644);
    err = writeWAVHeader(filedesc, hdr);
    if (err)
    {
        fprintf(stderr, "Error writing .wav header.");
        snd_pcm_close(handle);
        free(buffer);
        close(filedesc);
        return err;
    }
    fprintf(stdout, "Channels: %d\n", hdr->number_of_channels);
    for(int i = duration * 1000 / sampleRate; i > 0; i--)
    {
        err = snd_pcm_readi(handle, buffer, frames);
        if (err == -EPIPE) fprintf(stderr, "Overrun occurred: %d\n", err);
        if (err < 0) err = snd_pcm_recover(handle, err, 0);

        // Still an error, need to exit.
        if (err < 0)
        {
            fprintf(stderr, "Error occured while recording: %s\n", snd_strerror(err));
            snd_pcm_close(handle);
            free(buffer);
            close(filedesc);
            return err;
        }
        write(filedesc, buffer, size);
    }

    close(filedesc);
    snd_pcm_drain(handle);
    snd_pcm_close(handle);
    free(buffer);

    printf("Finished writing to %s\n", fileName);
    return 0;
}

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

    if(argc != 2)
    {
        fprintf(stderr, "Usage: %s (record duration)\n", argv[0]);
        return -1;
    }

    int err;
    struct WaveHeader *hdr;

    // Creates a temporary file in /tmp

    char wavFile[L_tmpnam + 5];
    char *tempFilenameStub = tmpnam(NULL);
    sprintf(wavFile, "%s.wav", tempFilenameStub);
    hdr = genericWAVHeader(44000, 16, 2);
    if (!hdr)
    {
        fprintf(stderr, "Error allocating WAV header.\n");
        return -1;
    }

    err = recordWAV(wavFile, hdr, 1000 * strtod(argv[1], NULL));
    if (err)
    {
            fprintf(stderr, "Error recording WAV file: %d\n", err);
            return err;
    }

    free(hdr);
    return 0;
}

Any suggestions?


Edit - I was told to compare the headers of my .wav file to that of one generated by arecord. Here are the results:

$ stat -c %s arecord.wav
352844

$ stat -c %s /tmp/filevXDDX6.wav
345004

$ xxd -g1 arecord.wav | head
0000000: 52 49 46 46 44 62 05 00 57 41 56 45 66 6d 74 20  RIFFDb..WAVEfmt
0000010: 10 00 00 00 01 00 02 00 44 ac 00 00 10 b1 02 00  ........D.......
0000020: 04 00 10 00 64 61 74 61 20 62 05 00 08 00 08 00  ....data b......
0000030: 08 00 08 00 07 00 07 00 fe ff fe ff f7 ff f7 ff  ................
0000040: f6 ff f6 ff ee ff ee ff ee ff ee ff f6 ff f6 ff  ................
0000050: f0 ff f0 ff e7 ff e7 ff ee ff ee ff f4 ff f4 ff  ................
0000060: f4 ff f4 ff f7 ff f7 ff fe ff fe ff fc ff fc ff  ................
0000070: fa ff fa ff f5 ff f5 ff ed ff ed ff ee ff ee ff  ................
0000080: f8 ff f8 ff f4 ff f4 ff ed ff ed ff ee ff ee ff  ................
0000090: f8 ff f8 ff f6 ff f6 ff f0 ff f0 ff ee ff ee ff  ................

$ xxd -g1 /tmp/filevXDDX6.wav | head
0000000: 52 49 46 46 44 62 05 00 57 41 56 45 66 6d 74 20  RIFFDb..WAVEfmt
0000010: 10 00 00 00 01 00 02 00 44 ac 00 00 10 b1 02 00  ........D.......
0000020: 04 00 10 00 64 61 74 61 20 62 05 00 71 00 71 00  ....data b..q.q.
0000030: 6f 00 6f 00 79 00 79 00 75 00 75 00 63 00 63 00  o.o.y.y.u.u.c.c.
0000040: 3e 00 3e 00 1b 00 1b 00 07 00 07 00 fb ff fb ff  >.>.............
0000050: 00 00 00 00 0c 00 0c 00 0f 00 0f 00 1c 00 1c 00  ................
0000060: 30 00 30 00 31 00 31 00 24 00 24 00 1e 00 1e 00  0.0.1.1.$.$.....
0000070: 24 00 24 00 31 00 31 00 2c 00 2c 00 28 00 28 00  $.$.1.1.,.,.(.(.
0000080: 31 00 31 00 3c 00 3c 00 36 00 36 00 31 00 31 00  1.1.<.<.6.6.1.1.
0000090: 39 00 39 00 40 00 40 00 3d 00 3d 00 30 00 30 00  9.9.@.@.=.=.0.0.

$ file arecord.wav
test.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, stereo 44100 Hz

$ file /tmp/filevXDDX6.wav                        
/tmp/filevXDDX6.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, stereo 44100 Hz
Community
  • 1
  • 1
syb0rg
  • 8,057
  • 9
  • 41
  • 81
  • @CL. Are you sure the headers will be the difference and not the actual data? – syb0rg Jul 01 '13 at 20:02
  • Failing between a write of 63906 bytes and 67687 bytes - which is suspiciously around `2^16`. is an `unsigned short` overflowing here? – marko Jul 01 '13 at 21:06
  • @Marko I'm pretty sure. The bytes are being stored in unsigned `int`s. Also, if I change the amount of seconds I record to something else (such as 5), it does not fail in the same area that it does for 2 seconds (it still fails right before the last frame is encoded). – syb0rg Jul 02 '13 at 01:33
  • Your for loop looks off, try `for(int i = (duration * sampleRate)/1000 ; i > 0; i-=frames)`, also I think it should be `hdr->file_size = pcm_data_size + 32;`, `data_size = hdr->file_size - 32;` – Musa Jul 02 '13 at 03:49
  • @Musa The modification of the `for` loop made the error occur sooner, and I'm not sure the file_size modification did anything. They also did not work together. – syb0rg Jul 02 '13 at 03:54
  • Well you should take CL advice and check your file against a valid wav file (of the same length) to see whats different. – Musa Jul 02 '13 at 04:00
  • @Musa I've done that and edited that into my question just now. They look exactly the same. – syb0rg Jul 02 '13 at 04:08
  • @syb0rg Use something like `xxd -g1 test.wav | head`. – CL. Jul 02 '13 at 07:16
  • @CL. I've edited that into my question. It looks like there is some kind of noise in my file (the data section). – syb0rg Jul 02 '13 at 16:30
  • A little bit of noise is normal for analog devices. Have the files exactly the same length? – CL. Jul 02 '13 at 17:31
  • @CL. Checking the file size for recording the same amount of time, no. I've edited that into the question. Using `soxi -D` tells me they are they both contain 2 seconds of audio. – syb0rg Jul 02 '13 at 17:54

1 Answers1

1

Your program does not write the correct number of samples to the .wav file.

snd_pcm_readi returns the number of frames that was actually read. You can only write that many frames to the output.

The integer division in duration * 1000 / sampleRate might be rounded. It is likely that duration * 1000 / sampleRate * frames is not exactly the same as the number of frames you actually want to read.

You should restructure your loop to count the total number of frames.

CL.
  • 173,858
  • 17
  • 217
  • 259
  • Alright, I figured out the `for` loop. I had to change the sampling rate down to `44000` in order for the loop count to be nice and even, but it works. Here's what I changed it to: `for(int i = ((duration * 1000) / (hdr->sample_rate / frames)); i > 0; i--)`. – syb0rg Jul 03 '13 at 19:23