Search code examples
cwavpcm

Wav file write and play multiplie time


I have a requirement, where I need to write a wav (with pcma data) file multiple times.

Say I have a file audio-g711a.wav. I want to write it to a new file say audio-g711a-out.wav 2 times. When I play audio-g711a-out.wav , it should play twice the longer duration than that of original file.

I did write using below code. However it plays for exactly same duration as that of original file (I was expecting it to play double the duration).

Code is as below.

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

#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int g_count = 0;

void init_config(int argc,char **argv);
void rewrite_file(int count);

int main(int argc, char **argv)
{
    init_config(argc, argv);
    printf("Count = %d\n", g_count);
    rewrite_file(g_count);
    return 0;
}

void rewrite_file(int count)
{
    int index;
    char arr[101];
    FILE *fd_in;
    FILE *fd_out;
    size_t len;
    unsigned long chunk_size;

    index = 0;
    fd_in = fopen("./audio-g711a.wav", "r");
    fd_out = fopen("./audio-g711a-out.wav", "w+");

rw_again:    
    index++;
    len = fread(arr, 1, 100, fd_in);
    while(len == 100)
    {
        fwrite(arr, 1, 100, fd_out);
        len = fread(arr, 1, 100, fd_in);
    }
    fwrite(arr, 1, len, fd_out);
    if(count > index)
    {
        printf("Completed %d round of operation of %d total rounds.\n", index, count);
        fclose(fd_in);
        fd_in = fopen("./audio-g711a.wav", "r");
        goto rw_again;
    }

    return;
}

void init_config(int argc,char **argv)
{
    int ch;

    while ((ch = getopt(argc, argv, "v:n:N:X")) != -1)
    {
        switch (ch)
        {
            case 'n':
            case 'N':
            {
                g_count = atoi(optarg);
            }
            break;

            default:
            break;

        }
    }

    return;
}

To execute this, one can execute like ./a.out -n 2

After some R&D, I realized wav files have some sort of header. When I write second time, I am writing the header again. That may cause the file not to play further. I stopped writing the header part (44 bytes) while writing second time. This did not solve the issue.

Can somebody please guide me how can I achieve writing a wav file for at least 2 times.

Update
The working code is as given below.

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

#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int g_count = 0;

void init_config(int argc,char **argv);
void rewrite_file(int count);

int main(int argc, char **argv)
{
    init_config(argc, argv);
    printf("Count = %d\n", g_count);
    rewrite_file(g_count);
    return 0;
}

void rewrite_file(int count)
{
    int index;
    char arr[101];
    FILE *fd_in;
    FILE *fd_out;
    size_t len;
    unsigned short data_index = 0;
    index = 0;
    fd_in = fopen("./audio-g711a.wav", "r");
    fd_out = fopen("./audio-g711a-out.wav", "w+");

    // copy header
    len = fread(arr, 1, 40, fd_in);
    fwrite(arr, 1, len, fd_out);

    if( strncmp("data", (arr+36), 4) == 0 )
    {
        data_index = 40;
    }
    else
    {
        len = fread(arr, 1, 14, fd_in);
        fwrite(arr, 1, len, fd_out);

        if( strncmp("data", (arr+10), 4) == 0 )
        {
            data_index = 54;
        }
    }


    // update header
    uint32_t dl;
    fseek(fd_in, data_index, SEEK_SET);
    fseek(fd_out, data_index, SEEK_SET);
    fread(&dl, sizeof(dl), 1, fd_in);
    dl *= count;
    fwrite(&dl, sizeof(dl), 1, fd_out);

    // copy data
rw_again:
    index++;
    fseek(fd_in, (4 + data_index), SEEK_SET);
    len = fread(arr, 1, 100, fd_in);
    while(len > 0)
    {
        fwrite(arr, 1, len, fd_out);
        len = fread(arr, 1, 100, fd_in);
    }
    fwrite(arr, 1, len, fd_out);
    if(count > index)
    {
        printf("Completed %d round of operation of %d total rounds.\n", index, count);
        fclose(fd_in);
        fd_in = fopen("./audio-g711a.wav", "r");
        goto rw_again;
    }

    fclose(fd_in);
    fclose(fd_out);

    return;
}
void init_config(int argc,char **argv)
{
    int ch;

    while ((ch = getopt(argc, argv, "v:n:N:X")) != -1)
    {
        switch (ch)
        {
            case 'n':
            case 'N':
            {
                g_count = atoi(optarg);
            }
            break;

            default:
            break;

        }
    }

    return;
}

Solution

  • The wav file has a header and a data section:

    [HEADER][DATA]
    

    A simple copy, as you do, produces the following file format:

    [HEADER][DATA][HEADER][DATA]
    

    What you need is:

    [HEADER][DATADATA]
         ^
         |
         +--- chunksize at offset 40 updated 
    

    Here is a quick hack:

    void rewrite_file(int count)
    {
        int index;
        char arr[101];
        FILE *fd_in;
        FILE *fd_out;
        size_t len;
        unsigned long chunk_size;
    
        index = 0;
        fd_in = fopen("./audio-g711a.wav", "r");
        fd_out = fopen("./audio-g711a-out.wav", "w+");
    
        // copy header
        len = fread(arr, 1, 40, fd_in);
        fwrite(arr, 1, len, fd_out);
    
        // update header
        uint32_t dl;
        fseek(fd_in, 40, SEEK_SET);
        fseek(fd_out, 40, SEEK_SET);
        fread(&dl, sizeof(dl), 1, fd_in);
        dl *= count;
        fwrite(&dl, sizeof(dl), 1, fd_out);
    
        // copy data
    rw_again:
        index++;
        fseek(fd_in, 44, SEEK_SET);
        len = fread(arr, 1, 100, fd_in);
        while(len > 0)
        {
            fwrite(arr, 1, len, fd_out);
            len = fread(arr, 1, 100, fd_in);
        }
        fwrite(arr, 1, len, fd_out);
        if(count > index)
        {
            printf("Completed %d round of operation of %d total rounds.\n", index, count);
            fclose(fd_in);
            fd_in = fopen("./audio-g711a.wav", "r");        
            goto rw_again;
        }
    
        fclose(fd_in);
        fclose(fd_out);
    
        return;
    }
    

    Update:

    If the wav file contains a 'fact' chunk (non-PCM formats), then data chunk size offset is not 40, but 54 for example. So it better to search the 'data'-tag to calculate the data chunk offset than to use the 40 as a magic number.