Files
2026-08-24 10:03:24 +08:00

132 lines
3.9 KiB
C

/*
* sndalign.c
* SPDX-FileCopyrightText: 2024-2025 BlueALSA developers
* SPDX-License-Identifier: MIT
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sndfile.h>
static bool is_silence(const short *src, unsigned int channels, size_t frames) {
for (size_t i = 0; i < channels * frames; i++)
if (src[i] != 0)
return false;
return true;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("Usage: %s <file1> <file2>\n", argv[0]);
return EXIT_FAILURE;
}
SNDFILE *sf1;
SF_INFO sf1_info = { 0 };
if ((sf1 = sf_open(argv[1], SFM_READ, &sf1_info)) == NULL) {
fprintf(stderr, "ERR: Couldn't open audio file: %s: %s\n", argv[1], sf_strerror(NULL));
return EXIT_FAILURE;
}
printf("Source 1: %s\n", argv[1]);
printf(" Frames: %ld\n", sf1_info.frames);
printf(" Rate: %d\n", sf1_info.samplerate);
printf(" Channels: %d\n", sf1_info.channels);
SNDFILE *sf2;
SF_INFO sf2_info = { 0 };
if ((sf2 = sf_open(argv[2], SFM_READ, &sf2_info)) == NULL) {
fprintf(stderr, "ERR: Couldn't open audio file: %s: %s\n", argv[2], sf_strerror(NULL));
return EXIT_FAILURE;
}
printf("Source 2: %s\n", argv[2]);
printf(" Frames: %ld\n", sf2_info.frames);
printf(" Rate: %d\n", sf2_info.samplerate);
printf(" Channels: %d\n", sf2_info.channels);
if (sf1_info.channels != sf2_info.channels) {
fprintf(stderr, "ERR: Channels mismatch: %d != %d\n", sf1_info.channels, sf2_info.channels);
return EXIT_FAILURE;
}
if (sf1_info.samplerate != sf2_info.samplerate) {
fprintf(stderr, "ERR: Sample rate mismatch: %d != %d\n", sf1_info.samplerate, sf2_info.samplerate);
return EXIT_FAILURE;
}
short *sf1_data = malloc(sf1_info.frames * sf1_info.channels * sizeof(short));
if (sf_readf_short(sf1, sf1_data, sf1_info.frames) != sf1_info.frames) {
fprintf(stderr, "ERR: Couldn't read audio data: %s\n", sf_strerror(sf1));
return EXIT_FAILURE;
}
if (is_silence(sf1_data, sf1_info.channels, sf1_info.frames)) {
fprintf(stderr, "ERR: Source 1 is all silence\n");
return EXIT_FAILURE;
}
short *sf2_data = malloc(sf2_info.frames * sf2_info.channels * sizeof(short));
if (sf_readf_short(sf2, sf2_data, sf2_info.frames) != sf2_info.frames) {
fprintf(stderr, "ERR: Couldn't read audio data: %s\n", sf_strerror(sf2));
return EXIT_FAILURE;
}
if (is_silence(sf2_data, sf2_info.channels, sf2_info.frames)) {
fprintf(stderr, "ERR: Source 2 is all silence\n");
return EXIT_FAILURE;
}
/* Calculate cross-correlation between two audio streams by applying
* different offsets while keeping the defined minimal overlap. */
const size_t channels = sf1_info.channels;
const size_t sf1_frames = sf1_info.frames;
const size_t sf2_frames = sf2_info.frames;
const size_t min_overlap = MIN(512, MIN(sf1_frames, sf2_frames));
const size_t cross_correlation_frames = sf1_frames + sf2_frames - min_overlap;
long long *cross_correlation = calloc(cross_correlation_frames, sizeof(long long));
#pragma omp parallel for schedule(dynamic)
for (size_t i = min_overlap; i < cross_correlation_frames; i++) {
size_t sf1_begin = i < sf2_frames ? 0 : i - sf2_frames;
size_t sf2_begin = i < sf2_frames ? sf2_frames - i : 0;
size_t sf1_end = i < sf1_frames ? i : sf1_frames;
const size_t overlap = sf1_end - sf1_begin;
long long cc_v = 0;
for (size_t j = 0; j < overlap; j++)
for (size_t k = 0; k < channels; k++)
cc_v +=
sf1_data[(sf1_begin + j) * channels + k] *
sf2_data[(sf2_begin + j) * channels + k];
#pragma omp atomic
cross_correlation[i] += cc_v;
}
ssize_t max_i = 0;
long long max_v = cross_correlation[0];
/* Find the maximum value in the cross-correlation array. */
for (size_t i = min_overlap; i < cross_correlation_frames; i++)
if (cross_correlation[i] > max_v)
max_v = cross_correlation[max_i = i];
printf("Best alignment: %zd\n", max_i - sf2_frames);
sf_close(sf1);
sf_close(sf2);
free(cross_correlation);
free(sf1_data);
free(sf2_data);
return EXIT_SUCCESS;
}