diff --git a/Makefile.common b/Makefile.common index ee4fa1c4cc..b2cf5972de 100644 --- a/Makefile.common +++ b/Makefile.common @@ -240,6 +240,7 @@ OBJ += frontend/frontend_driver.o \ ui/ui_companion_driver.o \ camera/camera_driver.o \ record/record_driver.o \ + record/drivers/record_wav.o \ command.o \ msg_hash.o \ intl/msg_hash_us.o \ diff --git a/griffin/griffin.c b/griffin/griffin.c index bc0af37006..f1a366c139 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1217,6 +1217,7 @@ WIFI RECORDING ============================================================ */ #include "../record/record_driver.c" +#include "../record/drivers/record_wav.c" #ifdef HAVE_FFMPEG #include "../record/drivers/record_ffmpeg.c" #endif diff --git a/record/drivers/record_ffmpeg.c b/record/drivers/record_ffmpeg.c index 0af5499872..63d66ebc4b 100644 --- a/record/drivers/record_ffmpeg.c +++ b/record/drivers/record_ffmpeg.c @@ -41,7 +41,7 @@ #include #endif -#include "../record_driver.h" +#include "record_ffmpeg.h" #ifdef __cplusplus extern "C" { diff --git a/record/drivers/record_ffmpeg.h b/record/drivers/record_ffmpeg.h new file mode 100644 index 0000000000..dd7e09bc40 --- /dev/null +++ b/record/drivers/record_ffmpeg.h @@ -0,0 +1,23 @@ +/* RetroArch - A frontend for libretro. + * Copyright (c) 2024 Aleksander Mazur + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef _RECORD_FFMPEG_H +#define _RECORD_FFMPEG_H + +#include "../record_driver.h" + +extern const record_driver_t record_ffmpeg; + +#endif diff --git a/record/drivers/record_wav.c b/record/drivers/record_wav.c new file mode 100644 index 0000000000..5dd3698d0f --- /dev/null +++ b/record/drivers/record_wav.c @@ -0,0 +1,205 @@ +/* RetroArch - A frontend for libretro. + * Copyright (c) 2024 Aleksander Mazur + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include "record_wav.h" +#include "../../verbosity.h" + +/**************************************/ + +/** WAV substructure. */ +typedef struct +{ + uint16_t tag; /**< Format tag. */ + uint16_t channels; /**< Number of channels. */ + uint32_t sps; /**< Samples per second. */ + uint32_t bps; /**< Bits per sample. */ + uint16_t block; /**< Bits per block (one sample for all channels). */ + uint16_t sample; /**< Bits per sample (single channel). */ +} waveformatex_t; + +/** WAV header. */ +typedef struct +{ + char riff[4]; /**< "RIFF" header tag. */ + uint32_t riff_size; /**< RIFF file size. */ + char fourcc[4]; /**< "WAVE" tag. */ + char fmt_tag[4]; /**< "fmt " chunk tag. */ + uint32_t fmt_size; /**< Size of the following "fmt " chunk. */ + waveformatex_t fmt; /**< Content of "fmt " chunk. */ + char data_tag[4]; /**< "data" chunk tag. */ + uint32_t data_size; /**< Size of the following "data" chunk. */ +} wav_hdr_t; + +/** Our private context related to a single recording. */ +typedef struct +{ + FILE *f; + unsigned frame; /**< Bytes per sample * number of channels. */ + uint32_t length; /**< Bytes of audio data recorded so far. */ +} record_wav_t; + +#define WAV_MAX_LENGTH (0xFFFFFFFF - sizeof(wav_hdr_t)) + +/**************************************/ + +static bool wav_write_hdr(record_wav_t *handle, unsigned channels, unsigned samplerate) +{ + wav_hdr_t header = { "RIFF", }; + uint32_t length; + + if (!handle || !handle->f) + return false; + + handle->frame = 2 * channels; + /* set initial size to 4 hours; to be fixed inside record_wav_finalize */ + length = 4 * 60 * 60 * samplerate * handle->frame; + header.riff_size = swap_if_big32(sizeof(wav_hdr_t) - offsetof(wav_hdr_t, fourcc) + length), + memcpy(header.fourcc, "WAVE", sizeof(header.fourcc)); + memcpy(header.fmt_tag, "fmt ", sizeof(header.fmt_tag)); + header.fmt_size = swap_if_big32(sizeof(header.fmt)); + header.fmt.tag = swap_if_big16(1); + header.fmt.channels = swap_if_big16(channels); + header.fmt.sps = swap_if_big16(samplerate); + header.fmt.bps = swap_if_big16(samplerate * handle->frame); + header.fmt.block = swap_if_big16(handle->frame); + header.fmt.sample = swap_if_big16(16); + memcpy(header.data_tag, "data", sizeof(header.data_tag)); + header.data_size = swap_if_big32(length); + + return fwrite(&header, sizeof(header), 1, handle->f) == 1; +} + +static bool write_le32_at(FILE *f, long offset, uint32_t value) +{ + if (fseek(f, offset, SEEK_SET)) + return false; + value = swap_if_big32(value); + if (fwrite(&value, sizeof(value), 1, f) != 1) + return false; + + return true; +} + +static bool wav_fix_hdr_and_close(record_wav_t *handle) +{ + if (!handle || !handle->f) + return false; + + if (!write_le32_at(handle->f, offsetof(wav_hdr_t, riff_size), + sizeof(wav_hdr_t) - offsetof(wav_hdr_t, fourcc) + handle->length)) + return false; + if (!write_le32_at(handle->f, offsetof(wav_hdr_t, data_size), + handle->length)) + return false; + fclose(handle->f); + handle->f = NULL; + return true; +} + +/**************************************/ + +static void *record_wav_new(const struct record_params *params) +{ + record_wav_t *handle = calloc(1, sizeof(*handle)); + if (!handle) + return NULL; + + do + { + handle->f = fopen(params->filename, "wb"); + if (!handle->f) + { + RARCH_ERR("[WAV]: Cannot create %s: %s\n", + params->filename, strerror(errno)); + break; + } + + if (!wav_write_hdr(handle, params->channels, params->samplerate)) + { + RARCH_ERR("[WAV]: Cannot write header to %s: %s\n", + params->filename, strerror(errno)); + break; + } + + return handle; + } while (0); + + free(handle); + return NULL; +} + +static bool record_wav_push_audio(void *data, + const struct record_audio_data *audio_data) +{ + record_wav_t *handle = (record_wav_t*)data; + size_t frames, max, bytes; + + if (!handle || !audio_data || !handle->f) + return false; + + frames = audio_data->frames; + max = (WAV_MAX_LENGTH - handle->length) / handle->frame; + if (frames > max) + frames = max; + bytes = frames * handle->frame; + if (fwrite(audio_data->data, bytes, 1, handle->f) != 1) + return false; + handle->length += bytes; + if (frames == max) + { + /* cannot append more data */ + RARCH_LOG("[WAV]: Size limit reached\n"); + if (!wav_fix_hdr_and_close(handle)) + return false; + } + return true; +} + +static bool record_wav_finalize(void *data) +{ + record_wav_t *handle = (record_wav_t*)data; + if (!handle) + return false; + + return wav_fix_hdr_and_close(handle); +} + +static void record_wav_free(void *data) +{ + record_wav_t *handle = (record_wav_t*)data; + if (!handle) + return; + + if (handle->f) + fclose(handle->f); + free(handle); +} + +const record_driver_t record_wav = { + record_wav_new, + record_wav_free, + NULL, + record_wav_push_audio, + record_wav_finalize, + "wav", +}; diff --git a/record/drivers/record_wav.h b/record/drivers/record_wav.h new file mode 100644 index 0000000000..15127296d6 --- /dev/null +++ b/record/drivers/record_wav.h @@ -0,0 +1,23 @@ +/* RetroArch - A frontend for libretro. + * Copyright (c) 2024 Aleksander Mazur + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef _RECORD_WAV_H +#define _RECORD_WAV_H + +#include "../record_driver.h" + +extern const record_driver_t record_wav; + +#endif diff --git a/record/record_driver.c b/record/record_driver.c index 5270ca2607..adda8d7960 100644 --- a/record/record_driver.c +++ b/record/record_driver.c @@ -31,6 +31,8 @@ #include "../defaults.h" #include "record_driver.h" +#include "drivers/record_ffmpeg.h" +#include "drivers/record_wav.h" static recording_state_t recording_state = {0}; @@ -47,6 +49,7 @@ const record_driver_t *record_drivers[] = { #ifdef HAVE_FFMPEG &record_ffmpeg, #endif + &record_wav, &record_null, NULL, }; diff --git a/record/record_driver.h b/record/record_driver.h index 58392d2230..ea7a1c0054 100644 --- a/record/record_driver.h +++ b/record/record_driver.h @@ -133,8 +133,6 @@ struct recording typedef struct recording recording_state_t; -extern const record_driver_t record_ffmpeg; - /** * config_get_record_driver_options: *