Câu hỏi Phân tích tệp WAV C (libsndfile, fftw3)


Tôi đang cố gắng phát triển một ứng dụng C đơn giản có thể đưa ra một giá trị từ 0-100 tại một dải tần số nhất định tại một dấu thời gian nhất định trong một tệp WAV.

Ví dụ: Tôi có dải tần số 44,1kHz (tệp MP3 thông thường) và tôi muốn chia phạm vi đó thành n số phạm vi (bắt đầu từ 0). Sau đó tôi cần phải có được biên độ của mỗi phạm vi, từ 0 đến 100.

Những gì tôi đã quản lý cho đến nay:

Sử dụng libsndfile Bây giờ tôi có thể đọc dữ liệu của một tệp WAV.

infile = sf_open(argv [1], SFM_READ, &sfinfo);

float samples[sfinfo.frames];

sf_read_float(infile, samples, 1);

Tuy nhiên, sự hiểu biết của tôi về FFT là khá hạn chế. Nhưng tôi biết nó cần thiết để có được biên độ ở các phạm vi tôi cần. Nhưng làm thế nào để tôi chuyển từ đây? Tôi tìm thấy thư viện FFTW-3, có vẻ phù hợp với mục đích này.

Tôi tìm thấy một số trợ giúp ở đây: https://stackoverflow.com/a/4371627/1141483

và xem hướng dẫn FFTW tại đây: http://www.fftw.org/fftw2_doc/fftw_2.html

Nhưng vì tôi không chắc chắn về hành vi của FFTW, tôi không biết tiến bộ từ đây.

Và một câu hỏi khác, giả sử bạn sử dụng libsndfile: Nếu bạn buộc đọc là đơn kênh (với một tệp âm thanh nổi) và sau đó đọc các mẫu. Sau đó, bạn có thực sự chỉ đọc một nửa số mẫu của tổng số tệp không? Một nửa trong số đó là từ kênh 1, hoặc tự động lọc chúng ra?

Cảm ơn một tấn cho sự giúp đỡ của bạn.

EDIT: Mã của tôi có thể được nhìn thấy ở đây:

double blackman_harris(int n, int N){
double a0, a1, a2, a3, seg1, seg2, seg3, w_n;
a0 = 0.35875;
a1 = 0.48829;
a2 = 0.14128;
a3 = 0.01168;

seg1 = a1 * (double) cos( ((double) 2 * (double) M_PI * (double) n) / ((double) N - (double) 1) );
seg2 = a2 * (double) cos( ((double) 4 * (double) M_PI * (double) n) / ((double) N - (double) 1) );
seg3 = a3 * (double) cos( ((double) 6 * (double) M_PI * (double) n) / ((double) N - (double) 1) );

w_n = a0 - seg1 + seg2 - seg3;
return w_n;
}

int main (int argc, char * argv [])
{   char        *infilename ;
SNDFILE     *infile = NULL ;
FILE        *outfile = NULL ;
SF_INFO     sfinfo ;


infile = sf_open(argv [1], SFM_READ, &sfinfo);

int N = pow(2, 10);

fftw_complex results[N/2 +1];
double samples[N];

sf_read_double(infile, samples, 1);


double normalizer;
int k;
for(k = 0; k < N;k++){
    if(k == 0){

        normalizer = blackman_harris(k, N);

    } else {
        normalizer = blackman_harris(k, N);
    }

}

normalizer = normalizer * (double) N/2;



fftw_plan p = fftw_plan_dft_r2c_1d(N, samples, results, FFTW_ESTIMATE);

fftw_execute(p);


int i;
for(i = 0; i < N/2 +1; i++){
    double value = ((double) sqrtf(creal(results[i])*creal(results[i])+cimag(results[i])*cimag(results[i]))/normalizer);
    printf("%f\n", value);

}



sf_close (infile) ;

return 0 ;
} /* main */

10
2018-05-16 22:15


gốc




Các câu trả lời:


Vâng tất cả phụ thuộc vào dải tần số bạn đang sau. Một FFT hoạt động bằng cách lấy 2 ^ n mẫu và cung cấp cho bạn 2 ^ (n-1) số thực và tưởng tượng. Tôi phải thừa nhận tôi khá mơ hồ về chính xác những giá trị này đại diện (Tôi có một người bạn đã hứa sẽ đi qua tất cả với tôi thay cho một khoản vay tôi đã làm cho anh ta khi anh ta có vấn đề tài chính;)) khác hơn một góc xung quanh một vòng tròn. Một cách hiệu quả, chúng cung cấp cho bạn một arccos của tham số góc cho sin và cosin cho mỗi thùng tần số mà từ đó các mẫu 2 ^ n ban đầu có thể được tái tạo hoàn hảo.

Dù sao điều này có lợi thế rất lớn mà bạn có thể tính toán cường độ bằng cách lấy khoảng cách euclide của các phần thực và tưởng tượng (sqrtf ((real * real) + (imag * imag))). Điều này cung cấp cho bạn một giá trị khoảng cách không chuẩn hóa. Giá trị này sau đó có thể được sử dụng để xây dựng một cường độ cho mỗi băng tần.

Vì vậy, cho phép có một đơn đặt hàng 10 FFT (2 ^ 10). Bạn nhập 1024 mẫu. Bạn FFT những mẫu và bạn nhận được 512 giá trị tưởng tượng và thực tế trở lại (thứ tự cụ thể của những giá trị phụ thuộc vào thuật toán FFT bạn sử dụng). Vì vậy, điều này có nghĩa rằng đối với một tập tin âm thanh 44.1Khz mỗi bin đại diện cho 44100/512 Hz hoặc ~ 86Hz mỗi thùng.

Một điều nên nổi bật từ điều này là nếu bạn sử dụng nhiều mẫu hơn (từ cái gọi là miền thời gian hoặc không gian khi xử lý tín hiệu đa chiều như hình ảnh), bạn sẽ có được đại diện tần số tốt hơn (gọi là miền tần số). Tuy nhiên bạn hy sinh một cho khác. Đây chỉ là cách mọi thứ đi và bạn sẽ phải sống với nó.

Về cơ bản bạn sẽ cần phải điều chỉnh các tần số thùng và thời gian / độ phân giải không gian để có được các dữ liệu bạn yêu cầu.

Đầu tiên một chút về danh pháp. Các mẫu miền thời gian 1024 tôi đã gọi trước đó được gọi là cửa sổ của bạn. Nói chung khi thực hiện loại quá trình này, bạn sẽ muốn trượt cửa sổ trên một số tiền để có được 1024 mẫu tiếp theo bạn FFT. Điều hiển nhiên cần làm là lấy mẫu 0-> 1023, rồi 1024-> 2047, v.v. Rất tiếc, điều này không mang lại kết quả tốt nhất. Lý tưởng nhất là bạn muốn chồng chéo các cửa sổ ở một mức độ nào đó để bạn có được sự thay đổi tần số mượt mà hơn theo thời gian. Phổ biến nhất mọi người trượt cửa sổ trên một nửa kích thước cửa sổ. tức là cửa sổ đầu tiên của bạn sẽ là 0-> 1023 thứ hai 512-> 1535, v.v.

Bây giờ điều này sau đó sẽ trả về một vấn đề nữa. Mặc dù thông tin này cung cấp cho việc tái tạo tín hiệu FFT nghịch đảo hoàn hảo, nó khiến bạn gặp vấn đề với tần suất bị rò rỉ vào thùng rác ở một mức độ nào đó. Để giải quyết vấn đề này một số nhà toán học (thông minh hơn tôi nhiều) đã đưa ra khái niệm về chức năng cửa sổ. Chức năng cửa sổ cung cấp sự tách biệt tần số tốt hơn trong miền tần số mặc dù dẫn đến mất thông tin trong miền thời gian (tức là không thể xây dựng lại hoàn toàn tín hiệu sau khi bạn đã sử dụng chức năng cửa sổ, AFAIK).

Bây giờ có nhiều loại chức năng cửa sổ khác nhau, từ cửa sổ hình chữ nhật (có hiệu quả không làm gì với tín hiệu) đến các chức năng khác nhau cung cấp cách ly tần số tốt hơn nhiều (mặc dù một số cũng có thể giết các tần số xung quanh mà bạn có thể quan tâm !!). Có, than ôi, không có một kích thước phù hợp với tất cả nhưng tôi là một fan hâm mộ lớn (cho spectrograms) của chức năng cửa sổ blackmann-harris. Tôi nghĩ nó mang lại kết quả tốt nhất!

Tuy nhiên như tôi đã đề cập trước đó FFT cung cấp cho bạn một quang phổ không chuẩn hóa. Để chuẩn hóa phổ (sau khi tính toán khoảng cách euclide), bạn cần chia tất cả các giá trị cho một hệ số chuẩn hóa (tôi đi vào chi tiết hơn đây).

bình thường hóa này sẽ cung cấp cho bạn một giá trị từ 0 đến 1. Vì vậy, bạn có thể dễ dàng nhiều giá trị này lên 100 để có được tỷ lệ từ 0 đến 100 của bạn.

Điều này, tuy nhiên, không phải là nơi nó kết thúc. Quang phổ bạn nhận được từ điều này là khá không hài lòng. Điều này là bởi vì bạn đang nhìn vào cường độ bằng cách sử dụng thang đo tuyến tính. Thật không may, tai người nghe bằng cách sử dụng thang đo logarit. Điều này thay vì gây ra vấn đề với cách thức một quang phổ / quang phổ trông.

Để làm tròn điều này, bạn cần chuyển đổi các giá trị 0 đến 1 này (tôi sẽ gọi nó là 'x') thành thang đo decibel. Việc chuyển đổi tiêu chuẩn là 20.0f * log10f (x). Sau đó, giá trị này sẽ cung cấp cho bạn một giá trị theo đó 1 đã chuyển đổi thành 0 và 0 đã được chuyển đổi thành -infinity. tầm quan trọng của bạn hiện đang ở quy mô lôgarít thích hợp. Tuy nhiên nó không phải lúc nào cũng hữu ích.

Tại thời điểm này, bạn cần phải nhìn vào chiều sâu bit mẫu ban đầu. Tại lấy mẫu 16 bit, bạn nhận được một giá trị nằm trong khoảng từ 32767 đến -32768. Điều này có nghĩa là của bạn phạm vi động là fabsf (20.0f * log10f (1.0f / 65536.0f)) hoặc ~ 96.33dB. Vì vậy, bây giờ chúng tôi có giá trị này.

Lấy các giá trị chúng ta có từ phép tính dB ở trên. Thêm giá trị -96,33 vào nó. Rõ ràng biên độ tối đa (0) bây giờ là 96,33. Bây giờ didivde bởi cùng một giá trị và bạn nowhave một giá trị khác nhau, từ-infinity đến 1.0f. Kẹp kết thúc thấp hơn xuống 0 và bây giờ bạn có một phạm vi từ 0 đến 1 và nhân với 100 và bạn có phạm vi từ 0 đến 100 cuối cùng của mình.

Và đó là nhiều hơn nữa của một bài quái vật hơn tôi đã dự định ban đầu nhưng nên cung cấp cho bạn một nền tảng tốt trong làm thế nào để tạo ra một phổ tốt / spectrogram cho một tín hiệu đầu vào.

và thở

Đọc thêm (đối với những người không phải là người đăng ban đầu đã tìm thấy nó):

Chuyển đổi FFT thành đồ thị

Chỉnh sửa: Là một sang một bên tôi thấy nụ hôn FFT dễ dàng hơn để sử dụng, mã của tôi để thực hiện một fft về phía trước là như sau:

CFFT::CFFT( unsigned int fftOrder ) :
    BaseFFT( fftOrder )
{
    mFFTSetupFwd    = kiss_fftr_alloc( 1 << fftOrder, 0, NULL, NULL );
}

bool CFFT::ForwardFFT( std::complex< float >* pOut, const float* pIn, unsigned int num )
{
    kiss_fftr( mFFTSetupFwd, pIn, (kiss_fft_cpx*)pOut );
    return true;
}

14
2018-05-17 22:00



Goz, bạn nghiêm túc là anh hùng của tôi. Cảm ơn một triệu cho sự giúp đỡ. Tôi đang đọc nó ngay bây giờ, và sẽ cố gắng thực hiện những gì bạn mô tả ngày mai :) - Thomas Kobber Panum
@ThomasKobberPanum: Không có probs :) - Goz
Xin chào, tôi đã đăng mã của tôi cho đến nay. Tôi chưa triển khai chồng chéo. Tôi chỉ cố gắng để có được một số giá trị chuẩn hóa để bắt đầu. Tôi không thể nhìn thấy những gì tôi đang làm sai? Tôi vẫn nhận được những con số khổng lồ này, điều này có ý nghĩa như giá trị bình thường là khá thấp ... nhưng nó phải không chính xác bằng cách nào đó? - Thomas Kobber Panum
@ThomasKobberPanum: Hãy xem ngay bây giờ. Là một sang một bên mặc dù bạn muốn được tốt nhất tắt bắt đầu một bài đăng hỏi các câu hỏi như you'l nhận được lưu lượng truy cập nhiều hơn cho câu hỏi của bạn (Tôi chắc chắn tôi không phải là người duy nhất có thể giúp). Chưa kể tôi chỉ có thể upvote câu hỏi của bạn một lần để bạn có thể có được một điểm số cao hơn theo cách đó;) - Goz
@ThomasKobberPanum: Ok 2 điều tôi nhận thấy. Bạn tạo tất cả các giá trị cửa sổ của bạn nhưng bạn không nhân mỗi giá trị trong cửa sổ của bạn bằng giá trị trả lại. Hơn nữa bạn chỉ đơn giản là sử dụng giá trị cuối cùng ra khỏi harman blackman như giá trị bình thường của bạn. Bạn tính toán giá trị chuẩn hóa bằng cách nhân tất cả các giá trị với nhau THEN chia cho N / 2. Nếu nó giúp tôi tính toán hệ số chuẩn hóa là 0.1793f, do đó bạn nên sử dụng nó;) - Goz