在音频处理过程中,我们有时会遇到特殊的问题,例如某些WAV文件的PCM数据发生了位移,导致声音播放异常。最近,我遇到了一个具体的问题,48000,32bit,8ch的PCM数据每32位(4字节)数据整体向后偏移了24位(3字节),即每个采样点并没有从正确的起始位置开始,而是从数据块的第24位开始存储。这篇文章将描述该问题的原因及其解决方法,并给出一个完整的代码示例。
问题描述
WAV文件是一种常见的音频文件格式,其PCM数据部分是按采样点顺序存储的。在标准的32位PCM格式中,每个采样点由4字节组成,数据应当按如下顺序排列:
| Byte 1 | Byte 2 | Byte 3 | Byte 4 |
然而,问题文件中的数据从第24位开始偏移,使得数据变为:
| (空) | (空) | (空) | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
这样的偏移会导致音频数据解析错误,播放时声音会发生异常。因此,我们需要对文件中的PCM数据重新排列,修正24位偏移。
解决思路
理解偏移:
数据偏移了24位,等同于偏移了3字节。这意味着我们需要跳过这3字节,从正确的位置读取数据。
数据重组:
将数据以32位(4字节)为一个采样点,修正每个采样点的起始位置。
处理对齐问题:
如果平台不支持未对齐内存访问,需要使用memcpy来安全地移动数据。
文件保存:
解析WAV文件头,读取并修正PCM数据,最后将结果保存为新的WAV文件。
修复前
修复后
解决方案实现
以下是完整的C代码实现,包含WAV文件的解析、数据偏移修正以及文件保存功能:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// WAV file header structure
typedef struct {
char riff[4]; // "RIFF" marker
uint32_t file_size; // Total file size minus 8 bytes
char wave[4]; // "WAVE" marker
char fmt_chunk_marker[4];// "fmt " subchunk marker
uint32_t fmt_chunk_size; // Size of the fmt chunk
uint16_t audio_format; // Audio format (1 = PCM)
uint16_t num_channels; // Number of channels
uint32_t sample_rate; // Sample rate
uint32_t byte_rate; // Byte rate
uint16_t block_align; // Block alignment
uint16_t bits_per_sample;// Bits per sample
char data_chunk_header[4];// "data" subchunk marker
uint32_t data_size; // Size of the data chunk
} WavHeader;
// Function to fix 24-bit offset in PCM data
void fix_bit_offset(const uint8_t *input, uint8_t *output, size_t total_bytes) {
size_t sample_count = total_bytes / sizeof(int32_t);
// Adjust the input buffer with a 24-bit offset (3 bytes)
for (size_t i = 0; i < sample_count; i++) {
//((int32_t *)output)[i] = ((const int32_t *)(input + 3))[i];
((int32_t *)(output + 1))[i] = ((const int32_t *)(input + 0))[i];
}
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <input WAV file> <output WAV file>\n", argv[0]);
return -1;
}
const char *input_file = argv[1];
const char *output_file = argv[2];
// Open input WAV file
FILE *input_fp = fopen(input_file, "rb");
if (!input_fp) {
fprintf(stderr, "Error: Could not open input file %s!\n", input_file);
return -1;
}
// Read and parse the WAV header
WavHeader header;
if (fread(&header, sizeof(WavHeader), 1, input_fp) != 1) {
fprintf(stderr, "Error: Failed to read WAV header!\n");
fclose(input_fp);
return -1;
}
// Validate the WAV file format
if (strncmp(header.riff, "RIFF", 4) != 0 || strncmp(header.wave, "WAVE", 4) != 0 || strncmp(header.data_chunk_header, "data", 4) != 0) {
fprintf(stderr, "Error: Invalid WAV file format!\n");
fclose(input_fp);
return -1;
}
// Check for PCM format
if (header.audio_format != 1 || header.bits_per_sample != 32) {
fprintf(stderr, "Error: Only 32-bit PCM WAV files are supported!\n");
fclose(input_fp);
return -1;
}
// Allocate memory for input and output buffers
size_t data_size = header.data_size;
uint8_t *input_data = (uint8_t *)malloc(data_size);
uint8_t *output_data = (uint8_t *)malloc(data_size + 4);
if (!input_data || !output_data) {
fprintf(stderr, "Error: Memory allocation failed!\n");
fclose(input_fp);
free(input_data);
free(output_data);
return -1;
}
// Read PCM data from the input file
size_t read_bytes = fread(input_data, 1, data_size, input_fp);
fclose(input_fp);
if (read_bytes != data_size) {
fprintf(stderr, "Error: Failed to read PCM data, read bytes: %zu (expected: %zu)\n", read_bytes, data_size);
free(input_data);
free(output_data);
return -1;
}
// Fix the 24-bit offset in the PCM data
fix_bit_offset(input_data, output_data, data_size);
// Update the WAV header for the output file
header.file_size = data_size + sizeof(WavHeader) - 8;
header.data_size = data_size;
// Save the corrected PCM data to the output WAV file
FILE *output_fp = fopen(output_file, "wb");
if (!output_fp) {
fprintf(stderr, "Error: Could not open output file %s!\n", output_file);
free(input_data);
free(output_data);
return -1;
}
// Write the WAV header to the output file
if (fwrite(&header, sizeof(WavHeader), 1, output_fp) != 1) {
fprintf(stderr, "Error: Failed to write WAV header!\n");
fclose(output_fp);
free(input_data);
free(output_data);
return -1;
}
// Write the corrected PCM data to the output file
size_t written_bytes = fwrite(output_data, 1, data_size, output_fp);
fclose(output_fp);
if (written_bytes != data_size) {
fprintf(stderr, "Error: Failed to write PCM data, written bytes: %zu (expected: %zu)\n", written_bytes, data_size);
free(input_data);
free(output_data);
return -1;
}
printf("Correction completed! The fixed WAV file has been saved to %s\n", output_file);
// Free allocated memory
free(input_data);
free(output_data);
return 0;
}
总结
通过上述代码,我们可以高效地修正WAV文件中因24位偏移导致的音频播放问题。这种解决方案不仅适用于当前问题,还可以作为处理其他PCM数据偏移问题的模板。
希望这篇文章能为有类似需求的朋友提供帮助!