mirror of https://github.com/mackron/miniaudio.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
9.3 KiB
C
306 lines
9.3 KiB
C
/*
|
|
USAGE: audioconverter [input file] [output file] [format] [channels] [rate]
|
|
|
|
EXAMPLES:
|
|
audioconverter my_file.flac my_file.wav
|
|
audioconverter my_file.flac my_file.wav f32 44100 linear --linear-order 8
|
|
*/
|
|
#define _CRT_SECURE_NO_WARNINGS /* For stb_vorbis' usage of fopen() instead of fopen_s(). */
|
|
|
|
#define STB_VORBIS_HEADER_ONLY
|
|
#include "../../extras/stb_vorbis.c" /* Enables Vorbis decoding. */
|
|
|
|
#define MA_NO_DEVICE_IO
|
|
#define MA_NO_THREADING
|
|
#define MINIAUDIO_IMPLEMENTATION
|
|
#include "../../miniaudio.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
void print_usage()
|
|
{
|
|
printf("USAGE: audioconverter [input file] [output file] [format] [channels] [rate]\n");
|
|
printf(" [format] is optional and can be one of the following:\n");
|
|
printf(" u8 8-bit unsigned integer\n");
|
|
printf(" s16 16-bit signed integer\n");
|
|
printf(" s24 24-bit signed integer (tightly packed)\n");
|
|
printf(" s32 32-bit signed integer\n");
|
|
printf(" f32 32-bit floating point\n");
|
|
printf(" [channels] is optional and in the range of %d and %d\n", MA_MIN_CHANNELS, MA_MAX_CHANNELS);
|
|
printf(" [rate] is optional and in the range of %d and %d\n", ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
|
printf("\n");
|
|
printf("PARAMETERS:\n");
|
|
printf(" --linear-order [0..%d]\n", MA_MAX_FILTER_ORDER);
|
|
}
|
|
|
|
ma_result do_conversion(ma_decoder* pDecoder, ma_encoder* pEncoder)
|
|
{
|
|
ma_result result = MA_SUCCESS;
|
|
|
|
MA_ASSERT(pDecoder != NULL);
|
|
MA_ASSERT(pEncoder != NULL);
|
|
|
|
/*
|
|
All we do is read from the decoder and then write straight to the encoder. All of the necessary data conversion
|
|
will happen internally.
|
|
*/
|
|
for (;;) {
|
|
ma_uint8 pRawData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
|
|
ma_uint64 framesReadThisIteration;
|
|
ma_uint64 framesToReadThisIteration;
|
|
|
|
framesToReadThisIteration = sizeof(pRawData) / ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels);
|
|
result = ma_decoder_read_pcm_frames(pDecoder, pRawData, framesToReadThisIteration, &framesReadThisIteration);
|
|
if (result != MA_SUCCESS) {
|
|
break; /* Reached the end, or an error occurred. */
|
|
}
|
|
|
|
/* At this point we have the raw data from the decoder. We now just need to write it to the encoder. */
|
|
ma_encoder_write_pcm_frames(pEncoder, pRawData, framesReadThisIteration, NULL);
|
|
|
|
/* Get out of the loop if we've reached the end. */
|
|
if (framesReadThisIteration < framesToReadThisIteration) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ma_bool32 is_number(const char* str)
|
|
{
|
|
if (str == NULL || str[0] == '\0') {
|
|
return MA_FALSE;
|
|
}
|
|
|
|
while (str[0] != '\0') {
|
|
if (str[0] < '0' || str[0] > '9') {
|
|
return MA_FALSE;
|
|
}
|
|
|
|
str += 1;
|
|
}
|
|
|
|
return MA_TRUE;
|
|
}
|
|
|
|
ma_bool32 try_parse_uint32_in_range(const char* str, ma_uint32* pValue, ma_uint32 lo, ma_uint32 hi)
|
|
{
|
|
ma_uint32 x;
|
|
|
|
if (!is_number(str)) {
|
|
return MA_FALSE; /* Not an integer. */
|
|
}
|
|
|
|
x = (ma_uint32)atoi(str);
|
|
if (x < lo || x > hi) {
|
|
return MA_FALSE; /* Out of range. */
|
|
}
|
|
|
|
if (pValue != NULL) {
|
|
*pValue = x;
|
|
}
|
|
|
|
return MA_TRUE;
|
|
}
|
|
|
|
ma_bool32 try_parse_format(const char* str, ma_format* pValue)
|
|
{
|
|
ma_format format;
|
|
|
|
/* */ if (strcmp(str, "u8") == 0) {
|
|
format = ma_format_u8;
|
|
} else if (strcmp(str, "s16") == 0) {
|
|
format = ma_format_s16;
|
|
} else if (strcmp(str, "s24") == 0) {
|
|
format = ma_format_s24;
|
|
} else if (strcmp(str, "s32") == 0) {
|
|
format = ma_format_s32;
|
|
} else if (strcmp(str, "f32") == 0) {
|
|
format = ma_format_f32;
|
|
} else {
|
|
return MA_FALSE; /* Not a format. */
|
|
}
|
|
|
|
if (pValue != NULL) {
|
|
*pValue = format;
|
|
}
|
|
|
|
return MA_TRUE;
|
|
}
|
|
|
|
ma_bool32 try_parse_channels(const char* str, ma_uint32* pValue)
|
|
{
|
|
return try_parse_uint32_in_range(str, pValue, MA_MIN_CHANNELS, MA_MAX_CHANNELS);
|
|
}
|
|
|
|
ma_bool32 try_parse_sample_rate(const char* str, ma_uint32* pValue)
|
|
{
|
|
return try_parse_uint32_in_range(str, pValue, ma_standard_sample_rate_min, ma_standard_sample_rate_max);
|
|
}
|
|
|
|
ma_bool32 try_parse_resample_algorithm(const char* str, ma_resample_algorithm* pValue)
|
|
{
|
|
ma_resample_algorithm algorithm;
|
|
|
|
/* */ if (strcmp(str, "linear") == 0) {
|
|
algorithm = ma_resample_algorithm_linear;
|
|
} else {
|
|
return MA_FALSE; /* Not a valid algorithm */
|
|
}
|
|
|
|
if (pValue != NULL) {
|
|
*pValue = algorithm;
|
|
}
|
|
|
|
return MA_TRUE;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
ma_result result;
|
|
ma_decoder_config decoderConfig;
|
|
ma_decoder decoder;
|
|
ma_encoder_config encoderConfig;
|
|
ma_encoder encoder;
|
|
ma_encoding_format outputEncodingFormat;
|
|
ma_format format = ma_format_unknown;
|
|
ma_uint32 channels = 0;
|
|
ma_uint32 rate = 0;
|
|
ma_resample_algorithm resampleAlgorithm;
|
|
ma_uint32 linearOrder = 8;
|
|
int iarg;
|
|
const char* pOutputFilePath;
|
|
|
|
print_usage();
|
|
|
|
/* Print help if requested. */
|
|
if (argc == 2) {
|
|
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
|
|
print_usage();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (argc < 3) {
|
|
print_usage();
|
|
return -1;
|
|
}
|
|
|
|
resampleAlgorithm = ma_resample_algorithm_linear;
|
|
|
|
/*
|
|
The fourth and fifth arguments can be a format and/or rate specifier. It doesn't matter which order they are in as we can identify them by whether or
|
|
not it's a number. If it's a number we assume it's a sample rate, otherwise we assume it's a format specifier.
|
|
*/
|
|
for (iarg = 3; iarg < argc; iarg += 1) {
|
|
if (strcmp(argv[iarg], "--linear-order") == 0) {
|
|
iarg += 1;
|
|
if (iarg >= argc) {
|
|
break;
|
|
}
|
|
|
|
if (!try_parse_uint32_in_range(argv[iarg], &linearOrder, 0, 8)) {
|
|
printf("Expecting a number between 0 and %d for --linear-order.\n", MA_MAX_FILTER_ORDER);
|
|
return -1;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (try_parse_resample_algorithm(argv[iarg], &resampleAlgorithm)) {
|
|
continue;
|
|
}
|
|
|
|
if (try_parse_format(argv[iarg], &format)) {
|
|
continue;
|
|
}
|
|
|
|
if (try_parse_channels(argv[iarg], &channels)) {
|
|
continue;
|
|
}
|
|
|
|
if (try_parse_sample_rate(argv[iarg], &rate)) {
|
|
continue;
|
|
}
|
|
|
|
/* Getting here means we have an unknown parameter. */
|
|
printf("Warning: Unknown parameter \"%s\"", argv[iarg]);
|
|
}
|
|
|
|
/* Initialize a decoder for the input file. */
|
|
decoderConfig = ma_decoder_config_init(format, channels, rate);
|
|
decoderConfig.resampling.algorithm = resampleAlgorithm;
|
|
decoderConfig.resampling.linear.lpfOrder = linearOrder;
|
|
|
|
result = ma_decoder_init_file(argv[1], &decoderConfig, &decoder);
|
|
if (result != MA_SUCCESS) {
|
|
printf("Failed to open input file. Check the file exists and the format is supported. Supported input formats:\n");
|
|
#if defined(dr_opus_h)
|
|
printf(" Opus\n");
|
|
#endif
|
|
#if defined(dr_mp3_h)
|
|
printf(" MP3\n");
|
|
#endif
|
|
#if defined(dr_flac_h)
|
|
printf(" FLAC\n");
|
|
#endif
|
|
#if defined(STB_VORBIS_INCLUDE_STB_VORBIS_H)
|
|
printf(" Vorbis\n");
|
|
#endif
|
|
#if defined(dr_wav_h)
|
|
printf(" WAV\n");
|
|
#endif
|
|
return (int)result;
|
|
}
|
|
|
|
|
|
pOutputFilePath = argv[2];
|
|
|
|
outputEncodingFormat = ma_encoding_format_wav; /* Wave by default in case we don't know the file extension. */
|
|
if (ma_path_extension_equal(pOutputFilePath, "wav")) {
|
|
outputEncodingFormat = ma_encoding_format_wav;
|
|
} else {
|
|
printf("Warning: Unknown file extension \"%s\". Encoding as WAV.\n", ma_path_extension(pOutputFilePath));
|
|
}
|
|
|
|
/* Initialize the encoder for the output file. */
|
|
encoderConfig = ma_encoder_config_init(outputEncodingFormat, decoder.outputFormat, decoder.outputChannels, decoder.outputSampleRate);
|
|
result = ma_encoder_init_file(pOutputFilePath, &encoderConfig, &encoder);
|
|
if (result != MA_SUCCESS) {
|
|
ma_decoder_uninit(&decoder);
|
|
printf("Failed to open output file. Check that the directory exists and that the file is not already opened by another process. %s", ma_result_description(result));
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* We have our decoder and encoder ready, so now we can do the conversion. */
|
|
result = do_conversion(&decoder, &encoder);
|
|
|
|
|
|
/* Done. */
|
|
ma_encoder_uninit(&encoder);
|
|
ma_decoder_uninit(&decoder);
|
|
|
|
return (int)result;
|
|
}
|
|
|
|
|
|
/* stb_vorbis implementation must come after the implementation of miniaudio. */
|
|
#if defined(_MSC_VER) && !defined(__clang__)
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4100) /* unreferenced formal parameter */
|
|
#pragma warning(disable:4244) /* '=': conversion from '' to '', possible loss of data */
|
|
#pragma warning(disable:4245) /* '=': conversion from '' to '', signed/unsigned mismatch */
|
|
#pragma warning(disable:4456) /* declaration of '' hides previous local declaration */
|
|
#pragma warning(disable:4457) /* declaration of '' hides function parameter */
|
|
#pragma warning(disable:4701) /* potentially uninitialized local variable '' used */
|
|
#else
|
|
#endif
|
|
#undef STB_VORBIS_HEADER_ONLY
|
|
#include "../../extras/stb_vorbis.c"
|
|
#if defined(_MSC_VER) && !defined(__clang__)
|
|
#pragma warning(pop)
|
|
#else
|
|
#endif
|