From c72269f77a449b69d7f6348f46ee646c8beefc2b Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 15 Jul 2023 21:15:31 -0300 Subject: [PATCH] Decoded FLAC to a valid WAV --- CMakeLists.txt | 26 +++++- cmake/FindLAME.cmake | 20 +++++ decoded.cpp | 202 +++++++++++++++++++++++++++++++++++++++++++ decoded.h | 44 ++++++++++ help.cpp | 7 ++ help.h | 3 + main.cpp | 97 ++++++++++++++++++++- 7 files changed, 396 insertions(+), 3 deletions(-) create mode 100644 cmake/FindLAME.cmake create mode 100644 decoded.cpp create mode 100644 decoded.h create mode 100644 help.cpp create mode 100644 help.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c2cb90..9ccc1b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,29 @@ cmake_minimum_required(VERSION 3.0) +project( + mlc + VERSION 0.0.1 + DESCRIPTION "Media Library Compiler: rips your media library to a lossy compilation" + LANGUAGES CXX +) +cmake_policy(SET CMP0076 NEW) +cmake_policy(SET CMP0079 NEW) -project(mlc) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -add_executable(mlc main.cpp) +find_package(LAME REQUIRED) +find_package(FLAC REQUIRED) + +add_executable(mlc + main.cpp + help.cpp + decoded.cpp +) + +target_link_libraries(mlc + ${LAME_LIBRARIES} + FLAC +) install(TARGETS mlc RUNTIME DESTINATION bin) diff --git a/cmake/FindLAME.cmake b/cmake/FindLAME.cmake new file mode 100644 index 0000000..e19bd79 --- /dev/null +++ b/cmake/FindLAME.cmake @@ -0,0 +1,20 @@ +#copied from here, thank you +#https://github.com/sipwise/sems/blob/master/cmake/FindLame.cmake + +FIND_PATH(LAME_INCLUDE_DIR lame/lame.h) +FIND_LIBRARY(LAME_LIBRARIES NAMES mp3lame) + +IF(LAME_INCLUDE_DIR AND LAME_LIBRARIES) + SET(LAME_FOUND TRUE) +ENDIF(LAME_INCLUDE_DIR AND LAME_LIBRARIES) + +IF(LAME_FOUND) + IF (NOT Lame_FIND_QUIETLY) + MESSAGE(STATUS "Found lame includes: ${LAME_INCLUDE_DIR}/lame/lame.h") + MESSAGE(STATUS "Found lame library: ${LAME_LIBRARIES}") + ENDIF (NOT Lame_FIND_QUIETLY) +ELSE(LAME_FOUND) + IF (Lame_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could NOT find lame development files") + ENDIF (Lame_FIND_REQUIRED) +ENDIF(LAME_FOUND) diff --git a/decoded.cpp b/decoded.cpp new file mode 100644 index 0000000..d141e6b --- /dev/null +++ b/decoded.cpp @@ -0,0 +1,202 @@ +#include "decoded.h" + +#include +#include +#include + +constexpr uint8_t waveDeclarationSize = 8; +constexpr uint8_t waveDescriptionSize = 36; +constexpr uint8_t waveHeaderSize = waveDeclarationSize + waveDescriptionSize; + +Decoded::Decoded(): + totalSamples(0), + sampleRate(0), + channels(0), + depth(0), + streamPosition(0), + stream(nullptr), + streamSize(0) +{} + +Decoded::~Decoded() { + if (streamInitialized()) + delete[] stream; +} + +bool Decoded::streamInitialized() const { + return sampleRate != 0; +} + +void Decoded::initializeStream(uint64_t total, uint32_t rate, uint16_t channels, uint16_t depth) { + if (streamInitialized()) + throw 1; + + totalSamples = total; + sampleRate = rate; + Decoded::channels = channels; + Decoded::depth = depth; + streamPosition = 0; + + streamSize = pcmSize() + waveHeaderSize; + stream = new uint8_t[streamSize]; +} + +void Decoded::printInfo() const { + std::cout << "sample rate : " << sampleRate << " Hz" << std::endl; + std::cout << "channels : " << channels << std::endl; + std::cout << "bits per sample : " << depth << std::endl; + std::cout << "total samples : " << totalSamples << std::endl; +} + +void Decoded::saveWaveToFile(const std::string& path) { + std::ofstream fp; + fp.open(path, std::ios::out | std::ios::binary); + fp.write((char*)stream, streamSize); + fp.close(); +} + +uint64_t Decoded::waveStremSize() const { + return streamSize; +} + + +void Decoded::write(uint16_t data) { + uint16_t* stream16 = (uint16_t*)stream; + stream16[streamPosition / 2] = data; + streamPosition += 2; +} + +void Decoded::write(std::string_view data) { + for (const char& letter : data) { + stream[streamPosition] = (uint8_t)letter; + ++streamPosition; + } +} + +void Decoded::write(uint32_t data) { + uint32_t* stream32 = (uint32_t*)stream; + stream32[streamPosition / 4] = data; + streamPosition += 4; +} + +void Decoded::writeWaveHeader() { + uint32_t pcm = pcmSize(); + uint8_t bytesPerSample = depth / 8; + + write("RIFF"); //4 + write(pcm + waveDescriptionSize); //8 + write("WAVEfmt "); //8 //16 + write((uint32_t)16); //12 //20 + write((uint16_t)1); //14 //22 + write(channels); //16 //24 + write(sampleRate); //20 //28 + write((uint32_t)(sampleRate * channels * bytesPerSample)); //24 //32 + write((uint16_t)(channels * bytesPerSample));//block align //26 //34 + write(depth); //28 //36 + write("data"); //32 //40 + write(pcm); //36 //44 +} + +uint64_t Decoded::pcmSize() const { + return totalSamples * channels * (depth / 8); +} + +void Decoded::FLACerror(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data) { + (void)decoder, (void)client_data; + std::cout << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl; +} + +void Decoded::FLACmetadata(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data) { + (void)(decoder); + Decoded* decoded = static_cast(client_data); + + /* print some stats */ + if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { + /* save for later */ + decoded->initializeStream( + metadata->data.stream_info.total_samples, + metadata->data.stream_info.sample_rate, + metadata->data.stream_info.channels, + metadata->data.stream_info.bits_per_sample + ); + + decoded->printInfo(); + } +} + +FLAC__StreamDecoderWriteStatus Decoded::FLACwrite( + const FLAC__StreamDecoder* decoder, + const FLAC__Frame* frame, + const FLAC__int32 * const buffer[], + void* client_data +) { + Decoded* decoded = static_cast(client_data); + + if (decoded->totalSamples == 0) { + std::cout << "ERROR: this example only works for FLAC files that have a total_samples count in STREAMINFO" << std::endl; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + if (decoded->channels != 2 || decoded->depth != 16) { + std::cout << "ERROR: this example only supports 16bit stereo streams" << std::endl; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + if (frame->header.channels != 2) { + std::cout << "ERROR: This frame contains " << frame->header.channels << " channels (should be 2)" << std::endl; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + if (buffer[0] == NULL) { + std::cout << "ERROR: buffer [0] is NULL" << std::endl; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + if (buffer[1] == NULL) { + std::cout << "ERROR: buffer [1] is NULL" << std::endl; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + /* write WAVE header before we write the first frame */ + if (frame->header.number.sample_number == 0) + decoded->writeWaveHeader(); + + /* write decoded PCM samples */ + for (size_t i = 0; i < frame->header.blocksize; i++) { + decoded->write((uint16_t)buffer[0][i]); + decoded->write((uint16_t)buffer[1][i]); + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +Decoded* Decoded::fromFLACFile(const std::string& path) { + FLAC__bool ok = true; + FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); + FLAC__StreamDecoderInitStatus init_status; + + Decoded* decoded = new Decoded; + + if (decoder == NULL) { + std::cout << "error allocating flac decoder" << std::endl; + throw 2; + } + FLAC__stream_decoder_set_md5_checking(decoder, true); + init_status = FLAC__stream_decoder_init_file(decoder, path.c_str(), FLACwrite, FLACmetadata, FLACerror, decoded); + + ok = init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK; + if (ok) { + std::cout << "error initializing decoder: " << FLAC__StreamDecoderInitStatusString[init_status] << std::endl; + } else { + ok = FLAC__stream_decoder_process_until_end_of_stream(decoder); + std::cout << "decoding: "; + if (ok) + std::cout << "succeeded"; + else + std::cout << "FAILED"; + + std::cout << std::endl; + std::cout << " state: " << FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)] << std::endl; + } + + FLAC__stream_decoder_delete(decoder); + + return decoded; +} + diff --git a/decoded.h b/decoded.h new file mode 100644 index 0000000..da6db6b --- /dev/null +++ b/decoded.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include "FLAC/stream_decoder.h" + +class Decoded { +public: + Decoded(); + ~Decoded(); + + void initializeStream(uint64_t total, uint32_t rate, uint16_t channels, uint16_t depth); + + bool streamInitialized() const; + void printInfo() const; + uint64_t pcmSize() const; + uint64_t waveStremSize() const; + void write(uint16_t data); + void write(uint32_t data); + void write(std::string_view data); + void writeWaveHeader(); + void saveWaveToFile(const std::string& path); + + static Decoded* fromFLACFile(const std::string& path); + + static void FLACerror(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data); + static void FLACmetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); + static FLAC__StreamDecoderWriteStatus FLACwrite( + const FLAC__StreamDecoder *decoder, + const FLAC__Frame *frame, + const FLAC__int32 * const buffer[], + void *client_data + ); + +private: + uint64_t totalSamples; + uint32_t sampleRate; + uint16_t channels; + uint16_t depth; + uint64_t streamPosition; + uint8_t* stream; + uint64_t streamSize; +}; + diff --git a/help.cpp b/help.cpp new file mode 100644 index 0000000..98be0d9 --- /dev/null +++ b/help.cpp @@ -0,0 +1,7 @@ +#include "help.h" + +#include "iostream" + +void printHelp() { + std::cout << "Sorry, there is no help yet. It will arrive one day... I hope" << std::endl; +} diff --git a/help.h b/help.h new file mode 100644 index 0000000..6e0427d --- /dev/null +++ b/help.h @@ -0,0 +1,3 @@ +#pragma once + +void printHelp (); diff --git a/main.cpp b/main.cpp index 8bb47f1..a1e2599 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,101 @@ #include +#include + +#include "FLAC/stream_decoder.h" +#include + +#include "help.h" +#include "decoded.h" int main(int argc, char **argv) { - std::cout << "Hello, world!" << std::endl; + if (argc < 2) { + std::cout << "Insufficient amount of arguments, launch with \"--help\" argument to see usage" << std::endl; + return 1; + } + + const std::string firstArgument(argv[1]); + + if (firstArgument == "--help") { + printHelp(); + return 0; + } + + Decoded* decoded = Decoded::fromFLACFile(firstArgument); + decoded->saveWaveToFile("./out.wav"); + // FILE *mp3 = fopen("output.mp3", "wb"); + // size_t nread; + // int ret, nwrite; + // + // // 1. Get lame version (OPTIONAL) + // std::cout << "Using LAME v" << get_lame_version() << std::endl; + // + // const int PCM_SIZE = 8192; + // const int MP3_SIZE = 8192; + // + // short pcm_buffer[PCM_SIZE * 2]; + // unsigned char mp3_buffer[MP3_SIZE]; + // + // // 2. Initializing + // lame_t lame = lame_init(); + // + // // 3. Do some settings (OPTIONAL) + // // lame_set_in_samplerate(lame, 48000); + // lame_set_VBR(lame, vbr_default); + // // lame_set_VBR_quality(lame, 2); + // + // // 4. Initialize parameters + // ret = lame_init_params(lame); + // if (ret < 0) { + // std::cout << "Error occurred during parameters initializing. Code = " << ret << std::endl; + // return 3; + // } + // + // do { + // // Read PCM_SIZE of array + // nread = fread(pcm_buffer, 2 * sizeof(short), PCM_SIZE, pcm); + // if (nread != 0) { + // // 5. Encode + // int nsamples = nread; + // short buffer_l[nsamples]; + // short buffer_r[nsamples]; + // + // printf("nread = %d\n", nread); + // printf("pcm_buffer.length = %d\n", sizeof(pcm_buffer)/sizeof(short)); + // + // int j = 0; + // int i = 0; + // for (i = 0; i < nsamples; i++) { + // buffer_l[i] = pcm_buffer[j++]; + // buffer_r[i] = pcm_buffer[j++]; + // + // } + // + // nwrite = lame_encode_buffer(lame, buffer_l, buffer_r, nread, + // mp3_buffer, MP3_SIZE); + // } else { + // // 6. Flush and give some final frames + // nwrite = lame_encode_flush(lame, mp3_buffer, MP3_SIZE); + // } + // + // if (nwrite < 0) { + // printf("Error occurred during encoding. Code = %d\n", nwrite); + // return 1; + // } + // + // fwrite(mp3_buffer, nwrite, 1, mp3); + // } while (nread != 0); + // + // // 7. Write INFO tag (OPTIONAL) + // // lame_mp3_tags_fid(lame, mp3); + // + // // 8. Free internal data structures + // lame_close(lame); + // + // fclose(mp3); + + + + delete decoded; + return 0; }