diff --git a/CMakeLists.txt b/CMakeLists.txt index 435ae90..74081da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,17 +3,20 @@ project(wasabi) set(CMAKE_CXX_STANDARD 14) -set(WAV_FORMAT_READER ./audio_format_readers/wav) +set(WAV_FORMAT_READER audio_format_readers/wav) +set(WASAPI audio_protocols/wasapi) #Include the directories and now your cpp files will recognize your headers include_directories(${WAV_FORMAT_READER}) +include_directories(${WASAPI}) set( SOURCE_FILES main.cpp ${WAV_FORMAT_READER}/wav_reader.hpp ${WAV_FORMAT_READER}/wav_reader.cpp -) + ${WASAPI}/wasapi.hpp + ${WASAPI}/wasapi.cpp) add_executable(wasabi ${SOURCE_FILES}) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "wasabi") \ No newline at end of file diff --git a/README.md b/README.md index 8cd4f76..e289cf1 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,11 @@ For now, it only works with LPCM encoded WAV audio files that have the same para #### TODO: - Implement WAVReader class constructor (rather than relying on default initialization) and destructor. - Add parameter validation. -- Add audio session volume control. -- Add audio playback control. +- Extend playback control by adding the possibility to skip forward and backward. +- Register a callback to receive notifications when the volume of the audio session has been changed using the Volume Mixer so that it is correctly updated when displayed on screen. - Convert the audio to the same format that the default audio output device is using. - Support more audio formats (such as other PCM types and non PCM encoded WAV files, FLAC, ALAC, AIFF, MP3, etc). -- DONE: Load the audio in chunks from a separate thread rather than doing it all at once, thus decreasing the wait time until the playback begins and considerably reducing the memory footprint. \ No newline at end of file +- DONE: + - Load the audio in chunks from a separate thread rather than doing it all at once, thus decreasing the wait time until the playback begins and considerably reducing the memory footprint. + - Add audio session volume control. + - Add audio playback control. \ No newline at end of file diff --git a/audio_format_readers/wav/wav_reader.cpp b/audio_format_readers/wav/wav_reader.cpp index 64fc50b..932e82c 100644 --- a/audio_format_readers/wav/wav_reader.cpp +++ b/audio_format_readers/wav/wav_reader.cpp @@ -121,7 +121,7 @@ void WAVReader::load_fmt_subchunk(std::shared_ptr file) { this->fmt_subchunk_size = file_fmt_size; } else { - std::cerr << "Error: Bad encoding, only PCM encoded WAV audio files are currently supported." << std::endl; + std::cerr << "Error: Bad encoding, only PCM encoded wav audio files are currently supported." << std::endl; exit(EXIT_FAILURE); } @@ -134,7 +134,7 @@ void WAVReader::load_fmt_subchunk(std::shared_ptr file) { this->audio_format = file_fmt_audio_format; } else { - std::cerr << "Error: Bad audio format, only linear PCM encoded WAV audio files are currently supported." + std::cerr << "Error: Bad audio format, only linear PCM encoded wav audio files are currently supported." << std::endl; exit(EXIT_FAILURE); @@ -247,6 +247,11 @@ void WAVReader::load_data_subchunk(std::shared_ptr file) { exit(EXIT_FAILURE); } + + float duration = this->data_subchunk_size / this->byte_rate; + + this->audio_duration.minutes = (int) (duration / 60); + this->audio_duration.seconds = (int) duration - (this->audio_duration.minutes * 60); }; void WAVReader::load_data(std::shared_ptr file) { @@ -285,7 +290,7 @@ void WAVReader::load_data(std::shared_ptr file) { file->close(); } -bool WAVReader::write_data(BYTE *dst_audio_buffer) { +bool WAVReader::get_chunk(BYTE **chunk, uint32_t &chunk_size) { bool stop = this->audio_buffer_chunks[this->current_audio_buffer_chunk].is_eof; std::unique_lock lck(this->mtx); @@ -302,7 +307,10 @@ bool WAVReader::write_data(BYTE *dst_audio_buffer) { this->is_playback_started = TRUE; } - memcpy(dst_audio_buffer, this->audio_buffer_chunks[this->current_audio_buffer_chunk].data, + *chunk = (BYTE *) malloc(this->audio_buffer_chunks[this->current_audio_buffer_chunk].size); + chunk_size = this->audio_buffer_chunks[this->current_audio_buffer_chunk].size; + + memcpy(*chunk, this->audio_buffer_chunks[this->current_audio_buffer_chunk].data, this->audio_buffer_chunks[this->current_audio_buffer_chunk].size); free(this->audio_buffer_chunks[this->current_audio_buffer_chunk].data); diff --git a/audio_format_readers/wav/wav_reader.hpp b/audio_format_readers/wav/wav_reader.hpp index 9d6cf09..95bd64e 100644 --- a/audio_format_readers/wav/wav_reader.hpp +++ b/audio_format_readers/wav/wav_reader.hpp @@ -1,5 +1,5 @@ -#ifndef WAVReader_H -#define WAVReader_H +#ifndef WASABI_WAVReader_H +#define WASABI_WAVReader_H #include #include @@ -55,10 +55,14 @@ class WAVReader { char data_subchunk_id[4]{}; uint32_t data_subchunk_size{}; uint32_t audio_buffer_chunk_size{}; + struct audio_duration { + int minutes; + int seconds; + } audio_duration; void load_file(std::string *file_path); - bool write_data(BYTE *buffer); + bool get_chunk(BYTE **chunk, uint32_t &chunk_size); }; -#endif \ No newline at end of file +#endif //WASABI_WAVReader_H \ No newline at end of file diff --git a/audio_protocols/wasapi/wasapi.cpp b/audio_protocols/wasapi/wasapi.cpp index 27597d7..6bc1819 100644 --- a/audio_protocols/wasapi/wasapi.cpp +++ b/audio_protocols/wasapi/wasapi.cpp @@ -1,48 +1,49 @@ -#include "WASAPI.hpp" +#include "wasapi.hpp" +#include #undef KSDATAFORMAT_SUBTYPE_PCM -const GUID KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, - {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - #define SAFE_RELEASE(pointer) if ((pointer) != NULL) {(pointer)->Release(); (pointer) = NULL;} -WASAPI::WASAPI(int rendering_endpoint_buffer_duration) { - this->rendering_endpoint_buffer_duration = rendering_endpoint_buffer_duration; +WASAPI::WASAPI(int buffer_duration) { + this->buffer_duration = buffer_duration; + this->output_device = nullptr; + this->format = (WAVEFORMATEX *) malloc(sizeof(WAVEFORMATEX)); + this->audio_client = nullptr; + this->audio_render_client = nullptr; + this->audio_volume_interface = nullptr; - this->get_default_audio_endpoint(); this->set_concurrency_mode(); + this->get_default_audio_endpoint(); + this->create_audio_client(); this->set_mix_format(); this->initialize_audio_client(); - this->initialize_audio_render_client(); + this->get_audio_render_client(); + this->get_audio_volume_interface(); } WASAPI::~WASAPI() { // Frees all allocated memory - CoTaskMemFree(closest_format); - SAFE_RELEASE(device_enumerator); - SAFE_RELEASE(device); - SAFE_RELEASE(audio_client); - SAFE_RELEASE(audio_render_client); + SAFE_RELEASE(this->output_device); + SAFE_RELEASE(this->audio_client); + SAFE_RELEASE(this->audio_render_client); + SAFE_RELEASE(this->audio_volume_interface); } void WASAPI::get_default_audio_endpoint() { - const int REFTIMES_PER_SEC = 10000000; - const int REFTIMES_PER_MILLISEC = 10000; - // Creates and initializes a device enumerator object and gets a reference to the interface that will be used to communicate with that object const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); - const IID IID_IAudioClient = __uuidof(IAudioClient); - const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); - const IID IID_ISimpleAudioVolume = __uuidof(ISimpleAudioVolume); - IMMDeviceEnumerator *device_enumerator = nullptr; + IMMDeviceEnumerator *device_enumerator; CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **) &device_enumerator); // Gets the reference to interface of the default audio endpoint - device_enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &device); + device_enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &this->output_device); + + // Frees allocated memory + SAFE_RELEASE(device_enumerator); } void WASAPI::set_concurrency_mode() { @@ -52,13 +53,33 @@ void WASAPI::set_concurrency_mode() { CoInitializeEx(nullptr, concurrency_model); } -void WASAPI::get_mix_formats() { +void WASAPI::create_audio_client() { + // Creates a COM object of the default audio endpoint with the audio client interface activated + this->output_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **) &this->audio_client); +} + +void WASAPI::find_best_mix_format(int &sample_rate, int &num_channels, int &bit_depth) { // Gets the audio format used by the audio client interface (mmsys.cpl -> audio endpoint properties -> advanced options) // TODO + + sample_rate = 48000; + num_channels = 2; + bit_depth = 16; } void WASAPI::set_mix_format() { + const GUID KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + + // + int sample_rate; + int num_channels; + int bit_depth; + + this->find_best_mix_format(sample_rate, num_channels, bit_depth); + + // WAVEFORMATEXTENSIBLE mix_format; mix_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; @@ -72,38 +93,51 @@ void WASAPI::set_mix_format() { mix_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; mix_format.dwChannelMask = KSAUDIO_SPEAKER_STEREO; - std::cout << "[Checking supported mix format]"; + std::cout << "[Checking supported mix format]" << std::endl; WAVEFORMATEX *closest_format = nullptr; HRESULT result = this->audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX *) &mix_format, - &closest_format); + &closest_format); if (result == S_OK) { std::cout << "\nThe mix format is supported!" << std::endl; - format = (WAVEFORMATEX *) &mix_format; + this->format->wFormatTag = WAVE_FORMAT_PCM; + this->format->nSamplesPerSec = mix_format.Format.nSamplesPerSec; + this->format->nChannels = mix_format.Format.nChannels; + this->format->wBitsPerSample = mix_format.Format.wBitsPerSample; + this->format->nBlockAlign = (mix_format.Format.nChannels * mix_format.Format.wBitsPerSample) / 8; + this->format->nAvgBytesPerSec = mix_format.Format.nSamplesPerSec * mix_format.Format.nBlockAlign; + this->format->cbSize = 0; } else if (result == S_FALSE) { std::cout << "WARNING: The requested mix format is not supported, a closest match will be tried instead (it may not work)." << std::endl; - format = closest_format; + this->format->wFormatTag = WAVE_FORMAT_PCM; + this->format->nSamplesPerSec = closest_format->nSamplesPerSec; + this->format->nChannels = closest_format->nChannels; + this->format->wBitsPerSample = closest_format->wBitsPerSample; + this->format->nBlockAlign = (closest_format->nChannels * closest_format->wBitsPerSample) / 8; + this->format->nAvgBytesPerSec = closest_format->nSamplesPerSec * closest_format->nBlockAlign; + this->format->cbSize = 0; } else { std::cerr << "ERROR: Unable to establish a supported mix format." << std::endl; exit(EXIT_FAILURE); } + + // Frees allocated memory + CoTaskMemFree(closest_format); } void WASAPI::initialize_audio_client() { - // Creates a COM object of the default audio endpoiint with the audio client interface activated - this->output_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **) &audio_client); + const int REFTIMES_PER_SEC = 10000000; + REFERENCE_TIME req_duration = this->buffer_duration * REFTIMES_PER_SEC; // Initializes the audio client interface - REFERENCE_TIME req_duration = rendering_endpoint_buffer_duration * REFTIMES_PER_SEC; - - HRESULT result = this->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, req_duration, 0, format, nullptr); + HRESULT result = this->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, req_duration, 0, this->format, nullptr); if (result != S_OK) { std::cerr << "ERROR: Unable to initialize audio client." << std::endl; @@ -112,74 +146,68 @@ void WASAPI::initialize_audio_client() { } } -void WASAPI::initialize_audio_render_client() { +void WASAPI::get_audio_render_client() { // Gets a reference to the audio render client interface of the audio client - IAudioRenderClient *audio_render_client; + this->audio_client->GetService(__uuidof(IAudioRenderClient), (void **) &this->audio_render_client); +} - this->audio_client->GetService(IID_IAudioRenderClient, (void **) &audio_render_client); - - // Declares audio buffer related variables - BYTE *buffer; - UINT32 num_buffer_frames; - UINT32 num_padding_frames; - DWORD buffer_audio_duration; +void WASAPI::write_chunk(BYTE *chunk, uint32_t chunk_size, bool stop) { + // Declares rendering endpoint buffer flags + DWORD buffer_flags = 0; // Gets the number of audio frames available in the rendering endpoint buffer + uint32_t num_buffer_frames; + this->audio_client->GetBufferSize(&num_buffer_frames); -} -void WASAPI::write_data(uint32 *chunk) { // Gets the amount of valid data that is currently stored in the buffer but hasn't been read yet + uint32_t num_padding_frames; + this->audio_client->GetCurrentPadding(&num_padding_frames); // Gets the amount of available space in the buffer num_buffer_frames -= num_padding_frames; // Retrieves a pointer to the next available memory space in the rendering endpoint buffer - audio_render_client->GetBuffer(num_buffer_frames, &buffer); + BYTE *buffer; - // TODO + this->audio_render_client->GetBuffer(num_buffer_frames, &buffer); - // Get the rendering endpoint buffer duration - buffer_audio_duration = - (DWORD) (num_buffer_frames / format->nSamplesPerSec) * - (REFTIMES_PER_SEC / REFTIMES_PER_MILLISEC); + // + memcpy(buffer, chunk, chunk_size); + free(chunk); + this->audio_client->GetCurrentPadding(&num_padding_frames); if (stop) { buffer_flags = AUDCLNT_BUFFERFLAGS_SILENT; } - audio_render_client->ReleaseBuffer(num_buffer_frames, buffer_flags); + this->audio_render_client->ReleaseBuffer(num_buffer_frames, buffer_flags); } void WASAPI::start() { - result = this->audio_client->Start(); + this->audio_client->Start(); } void WASAPI::stop() { this->audio_client->Stop(); } +void WASAPI::get_audio_volume_interface() { + // Gets a reference to the session volume control interface of the audio client + this->audio_client->GetService(__uuidof(ISimpleAudioVolume), (void **) &this->audio_volume_interface); +} + float WASAPI::get_volume() { - ISimpleAudioVolume *audio_volume; float current_volume; - // Gets a reference to the session volume control interface of the audio client - this->audio_client->GetService(IID_ISimpleAudioVolume, (void **) &audio_volume); - // Gets the session master volume of the audio client - audio_volume->GetMasterVolume(¤t_volume); + this->audio_volume_interface->GetMasterVolume(¤t_volume); return current_volume; } -void WASAPI::set_volume() { - ISimpleAudioVolume *audio_volume; - - // Gets a reference to the session volume control interface of the audio client - this->audio_client->GetService(IID_ISimpleAudioVolume, (void **) &audio_volume); - +void WASAPI::set_volume(float volume) { // Sets the session master volume of the audio client - float volume = 0.2; - audio_volume->SetMasterVolume(volume, nullptr); + this->audio_volume_interface->SetMasterVolume(volume, nullptr); } \ No newline at end of file diff --git a/audio_protocols/wasapi/wasapi.hpp b/audio_protocols/wasapi/wasapi.hpp index 43a62b2..71518f4 100644 --- a/audio_protocols/wasapi/wasapi.hpp +++ b/audio_protocols/wasapi/wasapi.hpp @@ -6,32 +6,38 @@ #include #include - class WASAPI { private: - IAudioClient *audio_client; IMMDevice *output_device; WAVEFORMATEX *format; - int rendering_endpoint_buffer_duration; + IAudioClient *audio_client; + IAudioRenderClient *audio_render_client; + ISimpleAudioVolume *audio_volume_interface; void set_concurrency_mode(); void get_default_audio_endpoint(); - void get_mix_format(); + void create_audio_client(); + + void find_best_mix_format(int &sample_rate, int &num_channels, int &bit_depth); void set_mix_format(); void initialize_audio_client(); - void initialize_audio_render_client(); + void get_audio_render_client(); + + void get_audio_volume_interface(); public: WASAPI(int rendering_endpoint_buffer_duration); ~WASAPI(); - void write_data(uint32_t *chunk); + int buffer_duration; + + void write_chunk(BYTE *chunk, uint32_t chunk_size, bool stop); void start(); @@ -39,7 +45,7 @@ class WASAPI { float get_volume(); - void set_volume(); + void set_volume(float volume); }; diff --git a/main.cpp b/main.cpp index f15c8e6..bc8c4f0 100644 --- a/main.cpp +++ b/main.cpp @@ -1,248 +1,238 @@ #include -#include -#include +#include "wasapi.hpp" #include "wav_reader.hpp" -#undef KSDATAFORMAT_SUBTYPE_PCM -const GUID KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, - {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - -using namespace std; - -#define SAFE_RELEASE(pointer) if ((pointer) != NULL) {(pointer)->Release(); (pointer) = NULL;} - -VOID play_audio_stream(string file_path, int rendering_endpoint_buffer_duration) { - const int REFTIMES_PER_SEC = 10000000; - const int REFTIMES_PER_MILLISEC = 10000; +void parse_args(int argc, char *argv[], std::string *file_path, int *rendering_endpoint_buffer_duration) { + // Checks if all parameters are provided, if not, initializes all required but non defined parameters with their default values + int file_pos = -1; + int rendering_endpoint_buffer_duration_pos = -1; - // Initializes the COM library for use by the calling thread and sets the thread's concurrency model - DWORD concurrency_model = COINIT_MULTITHREADED; + for (int i = 0; i < argc; i++) { + if (strcmp(argv[i], "--file") == TRUE) { + if ((i + 1) < (argc - 1)) { + file_pos = i + 1; + } + } else if (strcmp(argv[i], "--rendering_endpoint_buffer_duration") == TRUE) { + if ((i + 1) < (argc - 1)) { + rendering_endpoint_buffer_duration_pos = i + 1; + } + } + } - CoInitializeEx(nullptr, concurrency_model); + if (file_pos != -1) { + *file_path = argv[file_pos]; + } else { + std::string input_file_path; - // Creates and initializes a device enumerator object and gets a reference to the interface that will be used to communicate with that object - const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); - const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); - const IID IID_IAudioClient = __uuidof(IAudioClient); - const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); - const IID IID_ISimpleAudioVolume = __uuidof(ISimpleAudioVolume); + std::cout << "Input file: "; + std::getline(std::cin, input_file_path); - IMMDeviceEnumerator *device_enumerator = nullptr; + *file_path = input_file_path; + } - CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, - (void **) &device_enumerator); + if (rendering_endpoint_buffer_duration_pos != -1) { + *rendering_endpoint_buffer_duration = strtol(argv[rendering_endpoint_buffer_duration_pos], nullptr, 10); + } else { + *rendering_endpoint_buffer_duration = 1; + } +} - // Gets the reference to interface of the default audio endpoint - IMMDevice *device = nullptr; +void block_std_input() { + // Block standard input to be shown in the console + HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + DWORD mode; - device_enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &device); + if (GetConsoleMode(h, &mode)) { + mode &= ~ENABLE_ECHO_INPUT; - // Creates a COM object of the default audio endpoiint with the audio client interface activated - IAudioClient *audio_client; + SetConsoleMode(h, mode); + } +} - device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **) &audio_client); +void hide_console_cursor() { + HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_CURSOR_INFO info; - // Gets the audio format used by the audio client interface - WAVEFORMATEX *format; - WAVEFORMATEX *closest_format = nullptr; + info.dwSize = 100; + info.bVisible = FALSE; - // Set audio endpoint default format parameters (mmsys.cpl -> audio endpoint properties -> advanced options) - int num_channels = 2; - int bit_depth = 16; - int sample_rate = 48000; + SetConsoleCursorInfo(consoleHandle, &info); +} - WAVEFORMATEXTENSIBLE mix_format; - mix_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - mix_format.Format.nSamplesPerSec = sample_rate; - mix_format.Format.nChannels = num_channels; - mix_format.Format.wBitsPerSample = bit_depth; - mix_format.Format.nBlockAlign = (num_channels * bit_depth) / 8; - mix_format.Format.nAvgBytesPerSec = sample_rate * mix_format.Format.nBlockAlign; - mix_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - mix_format.Samples.wValidBitsPerSample = bit_depth; - mix_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - mix_format.dwChannelMask = KSAUDIO_SPEAKER_STEREO; +void clean_line(int num_chars) { + char blank_str[16]; - std::cout << "[Checking supported mix format]"; + sprintf(blank_str, "\r%%%is\r", num_chars); + printf(blank_str, ""); +} - HRESULT result = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX *) &mix_format, - &closest_format); +VOID play_audio_stream(std::string file_path, int rendering_endpoint_buffer_duration) { + // Declares variables to control the playback + bool stop = FALSE; + bool playing = FALSE; - if (result == S_OK) { - std::cout << "\nThe mix format is supported!" << std::endl; + // Instantiates a wasapi object + WASAPI wasapi = WASAPI(rendering_endpoint_buffer_duration); - format = (WAVEFORMATEX *) &mix_format; - } else if (result == S_FALSE) { - std::cout - << "WARNING: The requested mix format is not supported, a closest match will be tried instead (it may not work)." - << std::endl; + // Set audio session volume to the half of the current system volume + double volume = 0.5; + wasapi.set_volume(volume); - format = closest_format; - } else { - std::cerr << "ERROR: Unable to establish a supported mix format." << std::endl; + std::cout << "Rendering endpoint buffer duration: " << rendering_endpoint_buffer_duration * 1000 << " ms" + << std::endl; + std::cout << "Internal buffer duration: " << rendering_endpoint_buffer_duration * 5000 << " ms" << std::endl; - exit(EXIT_FAILURE); - } + // Instantiates a wav format reader object + WAVReader wav_reader = WAVReader(); + wav_reader.load_file(&file_path); - // Initializes the audio client interface - REFERENCE_TIME req_duration = rendering_endpoint_buffer_duration * REFTIMES_PER_SEC; + // Declares and initializes the variables that will keep track of the playing time + int current_minutes = 0; + int current_seconds = 0; + int current_milliseconds = 0; - result = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, req_duration, 0, format, nullptr); + // Declares and initializes the playback cycle in milliseconds + int cycle_duration = 100; - if (result != S_OK) { - std::cerr << "ERROR: Unable to initialize audio client." << std::endl; + // Declares the variables that will hold the chunk of audio data that will be written to the rendering endpoint buffer + BYTE *chunk = nullptr; + uint32_t chunk_size; - exit(EXIT_FAILURE); - } else { - // Gets a reference to the audio render client interface of the audio client - IAudioRenderClient *audio_render_client; + // Declares the variables that will store the status of some specific keys that are used to control the playback + SHORT space_key = 0x0; + SHORT up_key = 0x0; + SHORT down_key = 0x0; - audio_client->GetService(IID_IAudioRenderClient, (void **) &audio_render_client); + // Declares the variables that will control the playback + bool is_paused = FALSE; + bool is_window_focused = FALSE; - ISimpleAudioVolume *audio_volume; - // Gets a reference to the session volume control interface of the audio client - audio_client->GetService(IID_ISimpleAudioVolume, (void **) &audio_volume); + // Declares and initializes a variable that will hold the number of characters written to the console when updating the playback information + int num_chars_written = 0; - // Sets the session master volume of the audio client - float volume = 0.2; - audio_volume->SetMasterVolume(volume, nullptr); + // Declares the variable that will store the playback information + char *playback_status = (char *) malloc(42 * sizeof(char)); - // Declares audio buffer related variables - BYTE *buffer; - UINT32 num_buffer_frames; - UINT32 num_padding_frames; - DWORD buffer_audio_duration; + CONSOLE_SCREEN_BUFFER_INFO info; + COORD volume_cursor_position, current_time_cursor_position; - // Gets the number of audio frames available in the rendering endpoint buffer - audio_client->GetBufferSize(&num_buffer_frames); + while (stop == FALSE) { + if (!is_paused) { + if ((current_milliseconds + 1000) % (rendering_endpoint_buffer_duration * 1000) == 0) { + // Load the audio data chunk in the rendering endpoint buffer + stop = wav_reader.get_chunk(&chunk, chunk_size); - // Declares variables to control the playback - bool stop = FALSE; - bool playing = FALSE; + // Write the audio data chunk in the rendering endpoint buffer + wasapi.write_chunk(chunk, chunk_size, stop); - // Declares rendering endpoint buffer flags - DWORD buffer_flags = 0; + if (playing == FALSE) { + std::cout << std::endl << "[Starting to play the file]" << std::endl; + printf("Volume: %.1f\n", volume); - // Instantiates a WAV format reader - WAVReader wav_reader = WAVReader(); - wav_reader.load_file(&file_path); + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); + volume_cursor_position.X = 0; + volume_cursor_position.Y = info.dwCursorPosition.Y - 1; - // Declares and initializes the variables that will keep track of the playing time - int current_minutes = 0; - int current_seconds = 0; + current_time_cursor_position.X = 0; + current_time_cursor_position.Y = volume_cursor_position.Y + 2; - while (stop == FALSE) { - if ((current_seconds + 1) % rendering_endpoint_buffer_duration == 0) { - // Gets the amount of valid data that is currently stored in the buffer but hasn't been read yet - audio_client->GetCurrentPadding(&num_padding_frames); + printf("Audio duration: %dm %.2ds\n", wav_reader.audio_duration.minutes, wav_reader.audio_duration.seconds); - // Gets the amount of available space in the buffer - num_buffer_frames -= num_padding_frames; + wasapi.start(); - // Retrieves a pointer to the next available memory space in the rendering endpoint buffer - audio_render_client->GetBuffer(num_buffer_frames, &buffer); + playing = TRUE; + } + } - // Load the audio data in the rendering endpoint buffer - stop = wav_reader.write_data(buffer); + // Print the playback information + sprintf(playback_status, "\rCurrent time: %dm %.2ds", current_minutes, current_seconds); + printf(playback_status, 33); + fflush(stdout); - // Get the rendering endpoint buffer duration - buffer_audio_duration = - (DWORD) (num_buffer_frames / format->nSamplesPerSec) * - (REFTIMES_PER_SEC / REFTIMES_PER_MILLISEC); + if (current_milliseconds == 1000) { + current_seconds += 1; + current_milliseconds = 0; - if (stop) { - buffer_flags = AUDCLNT_BUFFERFLAGS_SILENT; + if (current_seconds == 60) { + current_minutes += 1; + current_seconds = 0; } + } - audio_render_client->ReleaseBuffer(num_buffer_frames, buffer_flags); - - if (playing == FALSE) { - result = audio_client->Start(); + current_milliseconds += cycle_duration; + } - if (result == S_OK) { - cout << endl << "[Starting to play the file]" << endl; - cout << "Number of available frames in rendering buffer: " << num_buffer_frames << endl; - cout << "Buffer duration: " << buffer_audio_duration << " ms" << endl; + // Check if a key was pressed + is_window_focused = (GetConsoleWindow() == GetForegroundWindow()); - float duration = wav_reader.data_subchunk_size / wav_reader.byte_rate; - int minutes = (int) (duration / 60); - int seconds = (int) duration - (minutes * 60); + if (is_window_focused) { + space_key = GetAsyncKeyState(VK_SPACE); + up_key = GetAsyncKeyState(VK_UP); + down_key = GetAsyncKeyState(VK_DOWN); + } - printf("Audio duration: %dm %.2ds\n", minutes, seconds); + if (space_key & 0x01) { + if (is_paused == FALSE) { + wasapi.stop(); - playing = TRUE; - } else { - exit(EXIT_FAILURE); - } - } - } + sprintf(playback_status + strlen(playback_status), " [PAUSED]", current_minutes, current_seconds); + num_chars_written = printf(playback_status); + fflush(stdout); - printf("\rCurrente time: %dm %.2ds", current_minutes, current_seconds); - fflush(stdout); + is_paused = TRUE; + } else { + wasapi.start(); - current_seconds += 1; + clean_line(num_chars_written); + sprintf(playback_status, "\rCurrent time: %dm %.2ds", current_minutes, current_seconds); + printf(playback_status, 33); + fflush(stdout); - if (current_seconds == 60) { - current_minutes += 1; - current_seconds = 0; + is_paused = FALSE; } - - // Sleep exactly one second - Sleep(1000); } - audio_client->Stop(); + if (up_key & 0x01 && volume < 1.0) { + volume += 0.1; - SAFE_RELEASE(audio_render_client); - } + if (volume > 1.0) { + volume = 1.0; + } - // Free all allocated memory - CoTaskMemFree(closest_format); - SAFE_RELEASE(device_enumerator); - SAFE_RELEASE(device); - SAFE_RELEASE(audio_client); -} + wasapi.set_volume(volume); -void parse_args(int argc, char *argv[], string *file_path, int *rendering_endpoint_buffer_duration) { - // Checks if all parameters are provided, if not, initializes all required but non defined parameters with their default values - int file_pos = -1; - int rendering_endpoint_buffer_duration_pos = -1; + SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), volume_cursor_position); + printf("Volume: %.1f\n", volume); + SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), current_time_cursor_position); + } - for (int i = 0; i < argc; i++) { - if (strcmp(argv[i], "--file") == TRUE) { - if ((i + 1) < (argc - 1)) { - file_pos = i + 1; - } - } else if (strcmp(argv[i], "--rendering_endpoint_buffer_duration") == TRUE) { - if ((i + 1) < (argc - 1)) { - rendering_endpoint_buffer_duration_pos = i + 1; + if (down_key & 0x01 && volume > 0.0) { + volume -= 0.1; + + if (volume < 0.0) { + volume = 0.0; } - } - } - if (file_pos != -1) { - *file_path = strtol(argv[file_pos], nullptr, 10); - } else { - string input_file_path; + wasapi.set_volume(volume); - std::cout << "Input file: "; - std::getline(std::cin, input_file_path); + SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), volume_cursor_position); + printf("Volume: %.1f\n", volume); + SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), current_time_cursor_position); + } - *file_path = input_file_path; + Sleep(cycle_duration); } - if (rendering_endpoint_buffer_duration_pos != -1) { - *rendering_endpoint_buffer_duration = strtol(argv[rendering_endpoint_buffer_duration_pos], nullptr, 10); - } else { - *rendering_endpoint_buffer_duration = 1; - } + wasapi.stop(); } int main(int argc, char *argv[]) { - string file; + std::string file; int rendering_endpoint_buffer_duration; parse_args(argc, argv, &file, &rendering_endpoint_buffer_duration); + block_std_input(); + hide_console_cursor(); play_audio_stream(file, rendering_endpoint_buffer_duration); return 0;