aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordvdli <70133153+dvdli@users.noreply.github.com>2021-03-15 15:15:32 +0800
committerGitHub <noreply@github.com>2021-03-15 15:15:32 +0800
commitc288f6e40e1b7fec05e91009dc6b7ff682aeb53d (patch)
treed696685895605c6752e4363dd00c21b205d22194
parentd7364cd3aebd62f8527c986afa7132b1f5271ed5 (diff)
parenta36d521c328b16b53360a016aa543491d0aad05e (diff)
Merge pull request #202 from dvdli/tinyalsa-float-dev
floating-point PCM support
-rw-r--r--Android.bp8
-rw-r--r--include/tinyalsa/pcm.h4
-rw-r--r--src/pcm.c10
-rw-r--r--tests/src/pcm_loopback_test.cc118
-rw-r--r--tests/src/pcm_test.cc2
-rw-r--r--utils/tinyplay.c110
6 files changed, 175 insertions, 77 deletions
diff --git a/Android.bp b/Android.bp
index 330393c..d6fec57 100644
--- a/Android.bp
+++ b/Android.bp
@@ -67,7 +67,7 @@ cc_binary {
name: "tinyplay2",
host_supported: true,
srcs: ["utils/tinyplay.c"],
- shared_libs: ["libtinyalsav2"],
+ static_libs: ["libtinyalsav2"],
cflags: ["-Werror"],
target: {
darwin: {
@@ -79,20 +79,20 @@ cc_binary {
cc_binary {
name: "tinycap2",
srcs: ["utils/tinycap.c"],
- shared_libs: ["libtinyalsav2"],
+ static_libs: ["libtinyalsav2"],
cflags: ["-Werror"],
}
cc_binary {
name: "tinymix2",
srcs: ["utils/tinymix.c"],
- shared_libs: ["libtinyalsav2"],
+ static_libs: ["libtinyalsav2"],
cflags: ["-Werror", "-Wall"],
}
cc_binary {
name: "tinypcminfo2",
srcs: ["utils/tinypcminfo.c"],
- shared_libs: ["libtinyalsav2"],
+ static_libs: ["libtinyalsav2"],
cflags: ["-Werror"],
}
diff --git a/include/tinyalsa/pcm.h b/include/tinyalsa/pcm.h
index b40550c..5c11e2a 100644
--- a/include/tinyalsa/pcm.h
+++ b/include/tinyalsa/pcm.h
@@ -178,6 +178,10 @@ enum pcm_format {
PCM_FORMAT_S24_3BE,
/** Signed, 32-bit, big endian */
PCM_FORMAT_S32_BE,
+ /** 32-bit float, little endian */
+ PCM_FORMAT_FLOAT_LE,
+ /** 32-bit float, big endian */
+ PCM_FORMAT_FLOAT_BE,
/** Max of the enumeration list, not an actual format. */
PCM_FORMAT_MAX
};
diff --git a/src/pcm.c b/src/pcm.c
index 10e477b..63ca65f 100644
--- a/src/pcm.c
+++ b/src/pcm.c
@@ -282,6 +282,11 @@ static unsigned int pcm_format_to_alsa(enum pcm_format format)
return SNDRV_PCM_FORMAT_S32_LE;
case PCM_FORMAT_S32_BE:
return SNDRV_PCM_FORMAT_S32_BE;
+
+ case PCM_FORMAT_FLOAT_LE:
+ return SNDRV_PCM_FORMAT_FLOAT_LE;
+ case PCM_FORMAT_FLOAT_BE:
+ return SNDRV_PCM_FORMAT_FLOAT_BE;
};
}
@@ -556,6 +561,8 @@ unsigned int pcm_format_to_bits(enum pcm_format format)
case PCM_FORMAT_S32_BE:
case PCM_FORMAT_S24_LE:
case PCM_FORMAT_S24_BE:
+ case PCM_FORMAT_FLOAT_LE:
+ case PCM_FORMAT_FLOAT_BE:
return 32;
case PCM_FORMAT_S24_3LE:
case PCM_FORMAT_S24_3BE:
@@ -1404,7 +1411,8 @@ again:
int pcm_state(struct pcm *pcm)
{
- int err = pcm_sync_ptr(pcm, 0);
+ // Update the state only. Do not sync HW sync.
+ int err = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
if (err < 0)
return err;
diff --git a/tests/src/pcm_loopback_test.cc b/tests/src/pcm_loopback_test.cc
index 6a3ffb8..5c6ff4d 100644
--- a/tests/src/pcm_loopback_test.cc
+++ b/tests/src/pcm_loopback_test.cc
@@ -64,6 +64,7 @@ public:
template<pcm_format F>
struct PcmFormat {
using Type = void;
+ static constexpr pcm_format kFormat = F;
static constexpr int32_t kMax = 0;
static constexpr int32_t kMin = 0;
};
@@ -71,10 +72,19 @@ struct PcmFormat {
template<>
struct PcmFormat<PCM_FORMAT_S16_LE> {
using Type = int16_t;
+ static constexpr pcm_format kFormat = PCM_FORMAT_S16_LE;
static constexpr Type kMax = std::numeric_limits<Type>::max();
static constexpr Type kMin = std::numeric_limits<Type>::min();
};
+template<>
+struct PcmFormat<PCM_FORMAT_FLOAT_LE> {
+ using Type = float;
+ static constexpr pcm_format kFormat = PCM_FORMAT_FLOAT_LE;
+ static constexpr Type kMax = 1.0;
+ static constexpr Type kMin = -1.0;
+};
+
// CH: channels
// SR: sampling rate
// FQ: sine wave frequency
@@ -139,43 +149,77 @@ static double Energy(T *buffer, size_t samples) {
return sum;
}
-TEST(PcmLoopbackTest, LoopbackS16le) {
+template<typename F>
+class PcmLoopbackTest : public ::testing::Test {
+ protected:
+ PcmLoopbackTest() = default;
+ virtual ~PcmLoopbackTest() = default;
+
+ void SetUp() override {
+ static constexpr pcm_config kInConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = kPcmForamt,
+ .start_threshold = 0,
+ .stop_threshold = 0,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+ pcm_in = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kInConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_in));
+
+ static constexpr pcm_config kOutConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = kPcmForamt,
+ .start_threshold = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+ pcm_out = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kOutConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_out));
+ ASSERT_EQ(pcm_link(pcm_in, pcm_out), 0);
+ }
+
+ void TearDown() override {
+ ASSERT_EQ(pcm_unlink(pcm_in), 0);
+ pcm_close(pcm_in);
+ pcm_close(pcm_out);
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+
static constexpr unsigned int kDefaultChannels = 2;
static constexpr unsigned int kDefaultSamplingRate = 48000;
static constexpr unsigned int kDefaultPeriodSize = 1024;
static constexpr unsigned int kDefaultPeriodCount = 3;
static constexpr unsigned int kDefaultPeriodTimeInMs =
kDefaultPeriodSize * 1000 / kDefaultSamplingRate;
+ static constexpr pcm_format kPcmForamt = F::Format;
+ pcm *pcm_in;
+ pcm *pcm_out;
+};
- static constexpr pcm_config kInConfig = {
- .channels = kDefaultChannels,
- .rate = kDefaultSamplingRate,
- .period_size = kDefaultPeriodSize,
- .period_count = kDefaultPeriodCount,
- .format = PCM_FORMAT_S16_LE,
- .start_threshold = 0,
- .stop_threshold = 0,
- .silence_threshold = 0,
- .silence_size = 0,
- };
- pcm *pcm_in = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kInConfig);
- ASSERT_TRUE(pcm_is_ready(pcm_in));
-
- static constexpr pcm_config kOutConfig = {
- .channels = kDefaultChannels,
- .rate = kDefaultSamplingRate,
- .period_size = kDefaultPeriodSize,
- .period_count = kDefaultPeriodCount,
- .format = PCM_FORMAT_S16_LE,
- .start_threshold = kDefaultPeriodSize,
- .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
- .silence_threshold = 0,
- .silence_size = 0,
- };
- pcm *pcm_out = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kOutConfig);
- ASSERT_TRUE(pcm_is_ready(pcm_out));
+using S16bitlePcmFormat = PcmFormat<PCM_FORMAT_S16_LE>;
+using FloatPcmFormat = PcmFormat<PCM_FORMAT_FLOAT_LE>;
- ASSERT_EQ(pcm_link(pcm_in, pcm_out), 0);
+using Formats = ::testing::Types<S16bitlePcmFormat, FloatPcmFormat>;
+
+TYPED_TEST_SUITE(PcmLoopbackTest, Formats);
+
+TYPED_TEST(PcmLoopbackTest, Loopback) {
+ static constexpr unsigned int kDefaultChannels = this->kDefaultChannels;
+ static constexpr unsigned int kDefaultSamplingRate = this->kDefaultSamplingRate;
+ static constexpr unsigned int kDefaultPeriodSize = this->kDefaultPeriodSize;
+ // static constexpr unsigned int kDefaultPeriodCount = this->kDefaultPeriodCount;
+ static constexpr unsigned int kDefaultPeriodTimeInMs = this->kDefaultPeriodTimeInMs;
+ static constexpr pcm_format kPcmForamt = this->kPcmForamt;
+ pcm *pcm_in = this->pcm_in;
+ pcm *pcm_out = this->pcm_out;
bool stopping = false;
ASSERT_EQ(pcm_get_subdevice(pcm_in), pcm_get_subdevice(pcm_out));
@@ -190,23 +234,26 @@ TEST(PcmLoopbackTest, LoopbackS16le) {
if (res == -1) {
std::cout << pcm_get_error(pcm_in) << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(kDefaultPeriodTimeInMs));
+ counter++;
continue;
}
- EXPECT_EQ(pcm_readi(pcm_in, buffer.get(), frames), frames) << counter;
+
// Test the energy of the buffer after the sine tone samples fill in the buffer.
// Therefore, check the buffer 5 times later.
if (counter >= 5) {
- double e = Energy(buffer.get(), frames * kInConfig.channels);
+ double e = Energy(buffer.get(), frames * kDefaultChannels);
EXPECT_GT(e, 0.0) << counter;
}
counter++;
}
+ std::cout << "read count = " << counter << std::endl;
});
std::thread playback([pcm_out, &stopping] {
- SineToneGenerator<2, 48000, 1000, 0, PCM_FORMAT_S16_LE> generator;
+ SineToneGenerator<kDefaultChannels, kDefaultSamplingRate, 1000, 0, kPcmForamt> generator;
size_t buffer_size = pcm_frames_to_bytes(pcm_out, kDefaultPeriodSize);
unsigned int frames = pcm_bytes_to_frames(pcm_out, buffer_size);
+ std::cout << buffer_size << std::endl;
auto buffer = std::make_unique<unsigned char[]>(buffer_size);
int32_t counter = 0;
while (!stopping) {
@@ -214,16 +261,13 @@ TEST(PcmLoopbackTest, LoopbackS16le) {
EXPECT_EQ(pcm_writei(pcm_out, buffer.get(), frames), frames) << counter;
counter++;
}
+ std::cout << "write count = " << counter << std::endl;
});
- std::this_thread::sleep_for(std::chrono::seconds(1));
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
stopping = true;
capture.join();
playback.join();
-
- ASSERT_EQ(pcm_unlink(pcm_in), 0);
- pcm_close(pcm_in);
- pcm_close(pcm_out);
}
} // namespace testing
diff --git a/tests/src/pcm_test.cc b/tests/src/pcm_test.cc
index 2668350..da114de 100644
--- a/tests/src/pcm_test.cc
+++ b/tests/src/pcm_test.cc
@@ -49,6 +49,8 @@ TEST(PcmTest, FormatToBits) {
ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_BE), 32);
ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_3BE), 24);
ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S32_BE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_FLOAT_LE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_FLOAT_BE), 32);
}
TEST(PcmTest, OpenAndCloseOutPcm) {
diff --git a/utils/tinyplay.c b/utils/tinyplay.c
index 4c7ccf6..96d0f60 100644
--- a/utils/tinyplay.c
+++ b/utils/tinyplay.c
@@ -27,11 +27,12 @@
*/
#include <tinyalsa/asoundlib.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
-#include <stdint.h>
#include <string.h>
-#include <signal.h>
#define OPTPARSE_IMPLEMENTATION
#include "optparse.h"
@@ -44,6 +45,7 @@ struct cmd {
int flags;
struct pcm_config config;
unsigned int bits;
+ bool is_float;
};
void cmd_init(struct cmd *cmd)
@@ -63,6 +65,7 @@ void cmd_init(struct cmd *cmd)
cmd->config.stop_threshold = cmd->config.period_size * cmd->config.period_count;
cmd->config.start_threshold = cmd->config.period_size;
cmd->bits = 16;
+ cmd->is_float = false;
}
#define ID_RIFF 0x46464952
@@ -70,6 +73,9 @@ void cmd_init(struct cmd *cmd)
#define ID_FMT 0x20746d66
#define ID_DATA 0x61746164
+#define WAVE_FORMAT_PCM 0x0001
+#define WAVE_FORMAT_IEEE_FLOAT 0x0003
+
struct riff_wave_header {
uint32_t riff_id;
uint32_t riff_sz;
@@ -100,10 +106,32 @@ struct ctx {
FILE *file;
};
-int ctx_init(struct ctx* ctx, const struct cmd *cmd)
+static bool is_wave_file(const char *filetype)
+{
+ return filetype != NULL && strcmp(filetype, "wav") == 0;
+}
+
+static bool signed_pcm_bits_to_format(int bits)
+{
+ switch (bits) {
+ case 8:
+ return PCM_FORMAT_S8;
+ case 16:
+ return PCM_FORMAT_S16_LE;
+ case 24:
+ return PCM_FORMAT_S24_3LE;
+ case 32:
+ return PCM_FORMAT_S32_LE;
+ default:
+ return -1;
+ }
+}
+
+static int ctx_init(struct ctx* ctx, struct cmd *cmd)
{
unsigned int bits = cmd->bits;
- struct pcm_config config = cmd->config;
+ struct pcm_config *config = &cmd->config;
+ bool is_float = cmd->is_float;
if (cmd->filename == NULL) {
fprintf(stderr, "filename not specified\n");
@@ -120,7 +148,7 @@ int ctx_init(struct ctx* ctx, const struct cmd *cmd)
return -1;
}
- if ((cmd->filetype != NULL) && (strcmp(cmd->filetype, "wav") == 0)) {
+ if (is_wave_file(cmd->filetype)) {
if (fread(&ctx->wave_header, sizeof(ctx->wave_header), 1, ctx->file) != 1){
fprintf(stderr, "error: '%s' does not contain a riff/wave header\n", cmd->filename);
fclose(ctx->file);
@@ -159,35 +187,31 @@ int ctx_init(struct ctx* ctx, const struct cmd *cmd)
fseek(ctx->file, ctx->chunk_header.sz, SEEK_CUR);
}
} while (more_chunks);
- config.channels = ctx->chunk_fmt.num_channels;
- config.rate = ctx->chunk_fmt.sample_rate;
+ config->channels = ctx->chunk_fmt.num_channels;
+ config->rate = ctx->chunk_fmt.sample_rate;
bits = ctx->chunk_fmt.bits_per_sample;
+ is_float = ctx->chunk_fmt.audio_format == WAVE_FORMAT_IEEE_FLOAT;
}
- if (bits == 8) {
- config.format = PCM_FORMAT_S8;
- } else if (bits == 16) {
- config.format = PCM_FORMAT_S16_LE;
- } else if (bits == 24) {
- config.format = PCM_FORMAT_S24_3LE;
- } else if (bits == 32) {
- config.format = PCM_FORMAT_S32_LE;
+ if (is_float) {
+ config->format = PCM_FORMAT_FLOAT_LE;
} else {
- fprintf(stderr, "bit count '%u' not supported\n", bits);
- fclose(ctx->file);
- return -1;
+ config->format = signed_pcm_bits_to_format(bits);
+ if (config->format == -1) {
+ fprintf(stderr, "bit count '%u' not supported\n", bits);
+ fclose(ctx->file);
+ return -1;
+ }
}
ctx->pcm = pcm_open(cmd->card,
cmd->device,
cmd->flags,
- &config);
- if (ctx->pcm == NULL) {
- fprintf(stderr, "failed to allocate memory for pcm\n");
- fclose(ctx->file);
- return -1;
- } else if (!pcm_is_ready(ctx->pcm)) {
- fprintf(stderr, "failed to open for pcm %u,%u\n", cmd->card, cmd->device);
+ config);
+ if (!pcm_is_ready(ctx->pcm)) {
+ fprintf(stderr, "failed to open for pcm %u,%u. %s\n",
+ cmd->card, cmd->device,
+ pcm_get_error(ctx->pcm));
fclose(ctx->file);
pcm_close(ctx->pcm);
return -1;
@@ -228,10 +252,11 @@ void print_usage(const char *argv0)
fprintf(stderr, "-d | --device <device number> The device to receive the audio\n");
fprintf(stderr, "-p | --period-size <size> The size of the PCM's period\n");
fprintf(stderr, "-n | --period-count <count> The number of PCM periods\n");
- fprintf(stderr, "-i | --file-type <file-type > The type of file to read (raw or wav)\n");
+ fprintf(stderr, "-i | --file-type <file-type> The type of file to read (raw or wav)\n");
fprintf(stderr, "-c | --channels <count> The amount of channels per frame\n");
fprintf(stderr, "-r | --rate <rate> The amount of frames per second\n");
fprintf(stderr, "-b | --bits <bit-count> The number of bits in one sample\n");
+ fprintf(stderr, "-f | --float The frames are in floating-point PCM\n");
fprintf(stderr, "-M | --mmap Use memory mapped IO to play audio\n");
}
@@ -250,6 +275,7 @@ int main(int argc, char **argv)
{ "channels", 'c', OPTPARSE_REQUIRED },
{ "rate", 'r', OPTPARSE_REQUIRED },
{ "bits", 'b', OPTPARSE_REQUIRED },
+ { "float", 'f', OPTPARSE_NONE },
{ "mmap", 'M', OPTPARSE_NONE },
{ "help", 'h', OPTPARSE_NONE },
{ 0, 0, 0 }
@@ -303,6 +329,15 @@ int main(int argc, char **argv)
case 'i':
cmd.filetype = opts.optarg;
break;
+ case 'b':
+ if (sscanf(opts.optarg, "%u", &cmd.bits) != 1) {
+ fprintf(stderr, "failed parsing bits per one sample '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'f':
+ cmd.is_float = true;
+ break;
case 'M':
cmd.flags |= PCM_MMAP;
break;
@@ -329,12 +364,13 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
- /* TODO get parameters from context */
- printf("playing '%s': %u ch, %u hz, %u bit\n",
- cmd.filename,
- cmd.config.channels,
- cmd.config.rate,
- cmd.bits);
+ printf("playing '%s': %u ch, %u hz, %u-bit ", cmd.filename, cmd.config.channels,
+ cmd.config.rate, pcm_format_to_bits(cmd.config.format));
+ if (cmd.config.format == PCM_FORMAT_FLOAT_LE) {
+ printf("floating-point PCM\n");
+ } else {
+ printf("signed PCM\n");
+ }
if (play_sample(&ctx) < 0) {
ctx_free(&ctx);
@@ -399,6 +435,7 @@ int play_sample(struct ctx *ctx)
size_t buffer_size = 0;
size_t num_read = 0;
size_t remaining_data_size = ctx->chunk_header.sz;
+ size_t played_data_size = 0;
size_t read_size = 0;
const struct pcm_config *config = pcm_get_config(ctx->pcm);
@@ -421,15 +458,18 @@ int play_sample(struct ctx *ctx)
read_size = remaining_data_size > buffer_size ? buffer_size : remaining_data_size;
num_read = fread(buffer, 1, read_size, ctx->file);
if (num_read > 0) {
- if (pcm_writei(ctx->pcm, buffer,
- pcm_bytes_to_frames(ctx->pcm, num_read)) < 0) {
- fprintf(stderr, "error playing sample\n");
+ int written_frames = pcm_writei(ctx->pcm, buffer,
+ pcm_bytes_to_frames(ctx->pcm, num_read));
+ if (written_frames < 0) {
+ fprintf(stderr, "error playing sample. %s\n", pcm_get_error(ctx->pcm));
break;
}
remaining_data_size -= num_read;
+ played_data_size += pcm_frames_to_bytes(ctx->pcm, written_frames);
}
} while (!close && num_read > 0 && remaining_data_size > 0);
+ printf("Played %zu bytes. Remains %zu bytes.\n", played_data_size, remaining_data_size);
pcm_wait(ctx->pcm, -1);
free(buffer);