Search code examples
cpipestdinsox

Delay occurred when reading bytes from stdin (Using pipe)


I am writing a C program that take sox's output as input for my program. Generally, my program would read the input from stdin and make some processing afterward. However, when I read byte values from stdin and wrote it back to another file (just to make sure everything is correct), I saw that my result was somehow be delayed (I am not sure about this), comparing to the original one (image is here, the waveform above is the output of sox's command).

Can someone point out for me where do I go wrong please? I have been struggled with this issue for so many hours. I am using Ubuntu 20.04. Thanks! (In case you want my audio file, here it is)

Sox command to generate above waveform

cat arctic_a0010.wav | sox -t wav - -b 16 -e signed -t raw - > mid.raw

Command to generate below waveform

cat arctic_a0010.wav | sox -t wav - -b 16 -e signed -t raw - | ./test

My minimal test.c program

#include <stdio.h>
#include <stdlib.h>

void storeValue(short* arr, short* assignValue, long int startPt, long int numBlock) {
    for (long int i = 0; i < numBlock; i++) {
        arr[startPt + i] = assignValue[i];
    }
}

void readFromStdin(short* arr, long* curSize) {
    long r, n = 0;
    int BUFFER_SIZE = 1024;
    short buffer[BUFFER_SIZE];

    freopen(NULL, "rb", stdin);
    while (1) {
        r = fread(buffer, sizeof(short), BUFFER_SIZE, stdin);
        if (r <= 0) {
            break;
        }
        if (n + r > *curSize) {
            *curSize = n + r;
            arr = (short*)realloc(arr, (*curSize) * sizeof(short));
        }
        storeValue(arr, buffer, n, r);
        n = n + r;
    }
}

int main(int argc, char *argv[])
{
    // Read from stdin
    short* inputArray = (short*)malloc(sizeof(short));
    long InpSize = 1, currentIndex = 0;
    readFromStdin(inputArray, &InpSize);

    // Write to file
    FILE *out = fopen("test.raw", "wb");
    fwrite(inputArray, sizeof(short), InpSize, out);
}

Solution

  • C passes arguments by value. That includes pointer arguments. Like all by-value arguments, changing the value within a function scope means nothing to the caller. If you want to convey a change in value to the caller there are multiple ways to do it, the most common shown below:


    Use That Otherwise-Worthless Return Value

    Right now your function returns void (e.g. nothing). Change it to send the (possibly updated) result of changes to arr. Like this:

    short *readFromStdin(short* arr, long* curSize) {
        long r, n = 0;
        int BUFFER_SIZE = 1024;
        short buffer[BUFFER_SIZE];
    
        freopen(NULL, "rb", stdin);
        while (1) {
            r = fread(buffer, sizeof(short), BUFFER_SIZE, stdin);
            if (r <= 0) {
                break;
            }
            if (n + r > *curSize) {
                *curSize = n + r;
                arr = realloc(arr, (*curSize) * sizeof *arr);
            }
            storeValue(arr, buffer, n, r);
            n = n + r;
        }
    
        return arr;
    }
    

    and in main :

    inputArray = readFromStdin(inputArray, &InpSize);
    

    Formal Pointer-To Argument an Pass By Address

    If you want to change an actual argument, you need to remember that C is pass-by-value. Therefore, you need to rig the parameter to accept an address of the 'thing' you want to change, and pass that address as the formal argument at the call site. Even the most novice C programmer is familiar with a simple integer swap:

    #include <stdio.h>
    
    void swap_int(int *pa, int *pb)
    {
        int tmp = *pa;
        *pa = *pb;
        *pb = tmp;
    }
    
    int main()
    {
        int a = 1, b = 2;
        swap_int(&a,&b);
        printf("%d %d\n", a, b);
    }
    

    Note this changes the values stored in a and b in main because their addresses are used as arguments to pointer-type formal parameters, and dereferencing allows us to drive data into them thereafter.

    Now, consider this:

    void make_me_bigger(int *arr, int *size)
    {
        *size += 10;
        arr = realloc(arr, *size * sizeof *arr);
    }
    

    There is no dereferencing going on with arr = It is no different than this

    int foo(int x)
    {
        x = 20; // only changes x locally
    }
    

    Calling foo from main

    int main()
    {
        int x = 10;
        foo(x);
        printf("%d\n", x); // still 10
    }
    

    If you want to pass something by "reference", the way to do it is to declare the formal parameter to be pointer-to-type (where 'type' is the underlying data type), and pass the address of your var, just like we did above in swap_int.

    This is true, even when the parameters are already pointer types. In those cases, the formal parameters become pointer-to-pointer-to-type, and the arguments from the call site are the addresses of pointer variables

    In other words, at long last:

    void readFromStdin(short **arr, long* curSize) {
        long r, n = 0;
        int BUFFER_SIZE = 1024;
        short buffer[BUFFER_SIZE];
    
        freopen(NULL, "rb", stdin);
        while (1) {
            r = fread(buffer, sizeof(short), BUFFER_SIZE, stdin);
            if (r <= 0) {
                break;
            }
            if (n + r > *curSize) {
                *curSize = n + r;
                *arr = realloc(*arr, (*curSize) * sizeof **arr);
            }
            storeValue(*arr, buffer, n, r);
            n = n + r;
        }
    }
    

    The plethora of other things (not checking your realloc results, casting mallocs, etc.) are all fuel for additional fixes, but the main problem can be addressed in either of the above techniques.