diff --git a/.gitignore b/.gitignore index bfd5ee2507a88b5ff0c4b8c185ab39d056e9f0bb..acf09d2b0104c7981c29d4b4fc7e0eb5b20348c4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ cmake_install.cmake .cache/* Debug/* compile_commands.json +build/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f11058346d8fdc2b869223465711b16b5258f79..3f1f09b48d35ce63ed520cc461be4accd6ee1c76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,16 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # endif() #endif() +set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) +find_package(Threads REQUIRED) +find_package(X11 REQUIRED) +find_library(AVCODEC_LIBRARY avcodec) +find_library(AVUTIL_LIBRARY avutil) +find_library(SWRESAMPLE_LIBRARY swresample) +find_library(AVFORMAT_LIBRARY avformat) set(PROJECT_SOURCES main.cpp @@ -51,6 +59,12 @@ else() endif() target_link_libraries(Violet PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) +target_link_libraries(Violet PRIVATE Threads::Threads) +target_link_libraries(Violet PRIVATE ${X11_LIBRARIES}) +target_link_libraries(Violet PRIVATE ${AVCODEC_LIBRARY}) +target_link_libraries(Violet PRIVATE ${AVUTIL_LIBRARY}) +target_link_libraries(Violet PRIVATE ${SWRESAMPLE_LIBRARY}) +target_link_libraries(Violet PRIVATE ${AVFORMAT_LIBRARY}) set_target_properties(Violet PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com diff --git a/mainwindow.cpp b/mainwindow.cpp index 6fdfb0fa2cfee732776ac651bef9441d3446ff24..067fadfe18d3e25bcee4c597dffec7798b5cfad9 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2,26 +2,15 @@ #include <iostream> #include <string.h> #include <stdio.h> +#include <CImg.h> -typedef struct WAV_HEADER { - uint8_t RIFF[4]; // RIFF Header Magic header - uint32_t chunkSize; // RIFF Chunk Size - uint8_t FORMAT[4]; // WAVE Header -} wav_header; - -typedef struct FTM_CHUNK_DATA { - uint16_t audioFormat; // Audio format - uint16_t numOfChan; // Number of channels - uint32_t sampleRate; // Sampling Frequency in Hz - uint32_t byteRate; // bytes per second - uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo - uint16_t bitsPerSample; // Number of bits per sample -} wav_fmt_chunk_data; - -typedef struct DATA_CHUNK_PARAM { - uint32_t chunkID; // "data" string - uint32_t chunkSize; // Sampled data length -} data_chunk_param; +extern "C" { +#include <libavutil/opt.h> +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libswresample/swresample.h> +#include <libavcodec/avfft.h> +} MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -45,31 +34,171 @@ MainWindow::~MainWindow() { } +int decode_audio_file(const char* path, const int sample_rate, double** data, int* size) { + + // initialize all muxers, demuxers and protocols for libavformat + // (does nothing if called twice during the course of one program execution) + av_register_all(); + + // get format from audio file + AVFormatContext* format = avformat_alloc_context(); + if (avformat_open_input(&format, path, NULL, NULL) != 0) { + fprintf(stderr, "Could not open file '%s'\n", path); + return -1; + } + if (avformat_find_stream_info(format, NULL) < 0) { + fprintf(stderr, "Could not retrieve stream info from file '%s'\n", path); + return -1; + } + + // Find the index of the first audio stream + int stream_index =- 1; + for (int i=0; i<format->nb_streams; i++) { + if (format->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + stream_index = i; + break; + } + } + if (stream_index == -1) { + fprintf(stderr, "Could not retrieve audio stream from file '%s'\n", path); + return -1; + } + AVStream* stream = format->streams[stream_index]; + + // find & open codec + AVCodecContext* codec = stream->codec; + if (avcodec_open2(codec, avcodec_find_decoder(codec->codec_id), NULL) < 0) { + fprintf(stderr, "Failed to open decoder for stream #%u in file '%s'\n", stream_index, path); + return -1; + } + + // prepare resampler + struct SwrContext* swr = swr_alloc(); + av_opt_set_int(swr, "in_channel_count", codec->channels, 0); + av_opt_set_int(swr, "out_channel_count", 1, 0); + av_opt_set_int(swr, "in_channel_layout", codec->channel_layout, 0); + av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_MONO, 0); + av_opt_set_int(swr, "in_sample_rate", codec->sample_rate, 0); + av_opt_set_int(swr, "out_sample_rate", sample_rate, 0); + av_opt_set_sample_fmt(swr, "in_sample_fmt", codec->sample_fmt, 0); + av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0); + swr_init(swr); + if (!swr_is_initialized(swr)) { + fprintf(stderr, "Resampler has not been properly initialized\n"); + return -1; + } + + // prepare to read data + AVPacket packet; + av_init_packet(&packet); + AVFrame* frame = av_frame_alloc(); + if (!frame) { + fprintf(stderr, "Error allocating the frame\n"); + return -1; + } + + // iterate through frames + *data = NULL; + *size = 0; + while (av_read_frame(format, &packet) >= 0) { + // decode one frame + int gotFrame; + if (avcodec_decode_audio4(codec, frame, &gotFrame, &packet) < 0) { + break; + } + if (!gotFrame) { + continue; + } + // resample frames + double* buffer; + av_samples_alloc((uint8_t**) &buffer, NULL, 1, frame->nb_samples, AV_SAMPLE_FMT_DBL, 0); + int frame_count = swr_convert(swr, (uint8_t**) &buffer, frame->nb_samples, (const uint8_t**) frame->data, frame->nb_samples); + // append resampled frames to data + *data = (double*) realloc(*data, (*size + frame->nb_samples) * sizeof(double)); + memcpy(*data + *size, buffer, frame_count * sizeof(double)); + *size += frame_count; + } + + // clean up + av_frame_free(&frame); + swr_free(&swr); + avcodec_close(codec); + avformat_free_context(format); + + // success + return 0; +} + +#define MAXPIXVALUE 12000 void MainWindow::openAudioFile(){ - QString filename = QFileDialog::getOpenFileName(this, "Select a file"); - wav_header wavHeader; - QMessageBox msg; - data_chunk_param dataChunkParam; - wav_fmt_chunk_data wavFmtChunkData; - FILE *wavFile = fopen(filename.toStdString().c_str(), "r"); - - fread(&wavHeader, 1, sizeof(wav_header), wavFile); - while (1) { - fread(&dataChunkParam, 1, sizeof(dataChunkParam), wavFile); - if (dataChunkParam.chunkID == 0x20746d66) - break; - fseek(wavFile, dataChunkParam.chunkSize, SEEK_CUR); - } - fread(&wavFmtChunkData, 1, dataChunkParam.chunkSize, wavFile); - while (1) { - fread(&dataChunkParam, 1, sizeof(dataChunkParam), wavFile); - if (dataChunkParam.chunkID == 0x61746164) - break; - fseek(wavFile, dataChunkParam.chunkSize, SEEK_CUR); - } - - uint16_t* data = (uint16_t*)malloc(dataChunkParam.chunkSize); - fread(data, 1, dataChunkParam.chunkSize, wavFile); - - fclose(wavFile); + QString filename = QFileDialog::getOpenFileName(this, "Select a file"); + QMessageBox msg; + FILE *audioFile = fopen(filename.toStdString().c_str(), "r"); + fclose(audioFile); + + int sample_rate = 44100; + double* data; + int size; + if (decode_audio_file(filename.toStdString().c_str(), sample_rate, &data, &size) != 0){ + printf("ERROR\n"); + return; + } + + /* + for (int j = size/2; j < size/2 + 20; j++){ + if (data[j] != 0) + fprintf(stderr, "data %d: %f\n", j, data[j]); + } + */ + + int i; // just some iterator indices + int chunk_size = 512; + int overlap = 128; + int decal = chunk_size - overlap; + fprintf(stderr, "size,chunk_size,overlap,decal: %d,%d,%d,%d\n", size, chunk_size, overlap, decal); + + FFTSample* chunk_data = (FFTSample*)av_malloc_array(2*chunk_size, sizeof(FFTSample)); + RDFTContext* ctx = av_rdft_init((int) log2(chunk_size), DFT_R2C); + int x = 0; + + int width = (size - chunk_size) / decal; + int height = chunk_size; + int16_t* pixs = (int16_t*)malloc(sizeof(int16_t) * width * height / 2); + + for (i = 0; i < size - chunk_size; i+= decal) { + for (int j = 0; j < chunk_size; j++){ + float curr_dat = data[i+j]; + double window_modifier = (0.5 * (1 - cos(2 * M_PI * j / (chunk_size - 1)))); // Hann (Hanning) window function + //float value = (float) (window_modifier * ((curr_dat) / 32768.0f)); // Convert to float and apply + float value = (float) (window_modifier * curr_dat); // Convert to float and apply + + // cap values above 1 and below -1 + if (value > 1.0) { + value = 1; + } else if (value < -1.0) { + value = -1; + } + chunk_data[j] = value; + } + + av_rdft_calc(ctx, chunk_data); + + for (int j = 0; j < chunk_size / 2; j++){ + float im = chunk_data[j*2]; + float re = chunk_data[j*2 + 1]; + double mag = sqrt(im * im + re * re); + /* + if ( i == 100*decal && j < 10 ) + fprintf(stderr, "im,re,mag: %f,%f,%f\n", im, re, mag); + */ + pixs[j*width+x] = (int16_t) (mag) * MAXPIXVALUE; + } + x++; + } + + cimg_library::CImg<int16_t> img(pixs, width, height / 2); + img.mirror("y").save_png("out.png"); + + av_rdft_end(ctx); + av_free(data); }