wav文件多次写入和播放

Posted

技术标签:

【中文标题】wav文件多次写入和播放【英文标题】:Wav file write and play multiplie time 【发布时间】:2015-09-11 11:30:51 【问题描述】:

我有一个需求,我需要多次编写一个 wav(带有 pcma 数据)文件。

假设我有一个文件 audio-g711a.wav。我想将它写入一个新文件,说 audio-g711a-out.wav 2 次。当我播放 audio-g711a-out.wav 时,它的播放时间应该是原始文件的两倍。

我确实使用下面的代码编写过。但是它的播放时长与原始文件的播放时长完全相同(我原以为它会播放两倍的时长)。

代码如下。

#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;

要执行此操作,可以像 ./a.out -n 2 一样执行

经过一些研发,我意识到 wav 文件有某种标题。当我第二次写时,我再次写标题。这可能会导致文件无法继续播放。我在第二次写入时停止写入标题部分(44 个字节)。这并没有解决问题。

有人可以指导我如何实现至少 2 次编写 wav 文件。

更新 工作代码如下。

#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;

【问题讨论】:

查看表头的格式。您需要更改标题的某些部分。 我在这里看到了格式codeproject.com/Articles/29676/…。但是不明白我需要做什么...... 标头只有 36 个字节。以下 8 个字节属于第一个数据块。 还需要将chunksize值(header中的偏移量4-7)增加适当的值。 看起来块大小是小端。对我来说,从第 4 到第 7 的原始文件字节是 0x32 0x40 0x3 0x0。既然我要写两次文件,我应该把它加倍吗? 【参考方案1】:

wav 文件有一个标题和一个数据部分:

[HEADER][DATA]

与您一样,一个简单的副本会生成以下文件格式:

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

你需要的是:

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

这是一个快速破解:

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;

更新:

如果 wav 文件包含“事实”块(非 PCM 格式),则数据块大小偏移量不是 40,而是例如 54。因此,最好搜索“数据”标签来计算数据块偏移量,而不是使用 40 作为幻数。

【讨论】:

按原样尝试了此代码。如果我使用 avplay 玩我会出错。 [wav @ 0x7f1b00004f80] no 'data' tag found audio-g711a-out.wav: 处理输入时发现无效数据 @user1861447 嗯,适用于我的测试文件。你能提供你的文件吗? 请分享您的电子邮件 ID。或者有什么方法可以通过堆栈溢出发送文件。 @user1861447 你文件中的chunksize偏移量是54,把40改成54,44改成58。 请查看我原帖中的更新代码。我是否正确假设数据标签出现在第 36 个索引或第 50 个索引处。是否有可能数据标签也可以出现在不同的索引处。【参考方案2】:

您需要将“数据”子块的“数据”部分加倍。

读取总块大小(偏移量 4)。

读取数据子块大小(偏移量 40)。

这两个值应该增加子块大小(如果大小的 4 个字节包含在子块大小值中,则可能为 -4)。

数据子块的数据部分(从偏移量 44 开始)应该加倍(写入两次)。

【讨论】:

以上是关于wav文件多次写入和播放的主要内容,如果未能解决你的问题,请参考以下文章

写入 WAV 文件 C++

如何将图形的输出写入 WAV 或 AIF 格式的外部音频文件?

AVAudioPlayer 无法播放使用 ExtAudioFileWriteAsync 编写的音频 (.wav) 文件

写入 wav 文件时定期创建静音

将 PCM 录制的数据写入 .wav 文件(java android)

如何从内存中播放 Wav 声音样本