Add simple WAV recording driver (audio only) (#16900)

Records unprocessed sound to a RIFF/WAVE (.wav) file,
up to 4 GB, no deps, no threads.
This commit is contained in:
Aleksander Mazur 2024-08-21 21:57:45 +02:00 committed by GitHub
parent 518d719f38
commit 35082c9538
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 257 additions and 3 deletions

View file

@ -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 \

View file

@ -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

View file

@ -41,7 +41,7 @@
#include <time.h>
#endif
#include "../record_driver.h"
#include "record_ffmpeg.h"
#ifdef __cplusplus
extern "C" {

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _RECORD_FFMPEG_H
#define _RECORD_FFMPEG_H
#include "../record_driver.h"
extern const record_driver_t record_ffmpeg;
#endif

205
record/drivers/record_wav.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <retro_endianness.h>
#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",
};

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _RECORD_WAV_H
#define _RECORD_WAV_H
#include "../record_driver.h"
extern const record_driver_t record_wav;
#endif

View file

@ -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,
};

View file

@ -133,8 +133,6 @@ struct recording
typedef struct recording recording_state_t;
extern const record_driver_t record_ffmpeg;
/**
* config_get_record_driver_options:
*