diff options
author | David Li <dvdli@google.com> | 2021-03-18 04:36:12 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-03-18 04:36:12 +0000 |
commit | 3e21898afa6b498ebdde6f0a621e02247886ded0 (patch) | |
tree | d696685895605c6752e4363dd00c21b205d22194 | |
parent | 882dbb1a2cf5e1cbccad294a5624444bdcf8ce4c (diff) | |
parent | 61254c737f5ac80bb1a1a66c6c1420e08bc16021 (diff) | |
download | tinyalsa_new-3e21898afa6b498ebdde6f0a621e02247886ded0.tar.gz |
Merge remote-tracking branch 'remotes/aosp/upstream-master' into am: 62e402fb24 am: 6f0089016a am: 61254c737f
Original change: https://android-review.googlesource.com/c/platform/external/tinyalsa_new/+/1639959
Change-Id: If409a3721cc0d66e066e319e658f84b1713ef1fe
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | Android.bp | 8 | ||||
-rw-r--r-- | include/tinyalsa/pcm.h | 8 | ||||
-rw-r--r-- | src/pcm.c | 88 | ||||
-rw-r--r-- | tests/src/mixer_test.cc | 2 | ||||
-rw-r--r-- | tests/src/pcm_in_test.cc | 1 | ||||
-rw-r--r-- | tests/src/pcm_loopback_test.cc | 118 | ||||
-rw-r--r-- | tests/src/pcm_out_test.cc | 6 | ||||
-rw-r--r-- | tests/src/pcm_test.cc | 2 | ||||
-rw-r--r-- | utils/tinymix.c | 2 | ||||
-rw-r--r-- | utils/tinyplay.c | 113 |
11 files changed, 235 insertions, 115 deletions
diff --git a/.travis.yml b/.travis.yml index 28667cb..4186666 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ os: linux -dist: xenial +dist: bionic sudo: false language: c compiler: @@ -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 6569146..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 }; @@ -335,9 +339,9 @@ int pcm_write(struct pcm *pcm, const void *data, unsigned int count) TINYALSA_DE int pcm_read(struct pcm *pcm, void *data, unsigned int count) TINYALSA_DEPRECATED; -int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count); +int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count) TINYALSA_DEPRECATED; -int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count); +int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count) TINYALSA_DEPRECATED; int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, unsigned int *frames); @@ -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: @@ -995,24 +1002,25 @@ int pcm_close(struct pcm *pcm) * - @ref PCM_MONOTONIC * @param config The hardware and software parameters to open the PCM with. * @returns A PCM structure. - * If an error occurs allocating memory for the PCM, NULL is returned. - * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready. - * If @ref pcm_is_ready, check @ref pcm_get_error for more information. + * If an error occurs, the pointer of bad_pcm is returned. + * Otherwise, it returns the pointer of PCM object. + * Client code should check that the PCM opened properly by calling @ref pcm_is_ready. + * If @ref pcm_is_ready returns false, check @ref pcm_get_error for more information. * @ingroup libtinyalsa-pcm */ struct pcm *pcm_open_by_name(const char *name, unsigned int flags, const struct pcm_config *config) { - unsigned int card, device; - if ((name[0] != 'h') - || (name[1] != 'w') - || (name[2] != ':')) { - return NULL; - } else if (sscanf(&name[3], "%u,%u", &card, &device) != 2) { - return NULL; - } - return pcm_open(card, device, flags, config); + unsigned int card, device; + if (name[0] != 'h' || name[1] != 'w' || name[2] != ':') { + oops(&bad_pcm, 0, "name format is not matched"); + return &bad_pcm; + } else if (sscanf(&name[3], "%u,%u", &card, &device) != 2) { + oops(&bad_pcm, 0, "name format is not matched"); + return &bad_pcm; + } + return pcm_open(card, device, flags, config); } /** Opens a PCM. @@ -1029,9 +1037,10 @@ struct pcm *pcm_open_by_name(const char *name, * - @ref PCM_MONOTONIC * @param config The hardware and software parameters to open the PCM with. * @returns A PCM structure. - * If an error occurs allocating memory for the PCM, NULL is returned. - * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready. - * If @ref pcm_is_ready, check @ref pcm_get_error for more information. + * If an error occurs, the pointer of bad_pcm is returned. + * Otherwise, it returns the pointer of PCM object. + * Client code should check that the PCM opened properly by calling @ref pcm_is_ready. + * If @ref pcm_is_ready returns false, check @ref pcm_get_error for more information. * @ingroup libtinyalsa-pcm */ struct pcm *pcm_open(unsigned int card, unsigned int device, @@ -1042,8 +1051,10 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, int rc; pcm = calloc(1, sizeof(struct pcm)); - if (!pcm) + if (!pcm) { + oops(&bad_pcm, ENOMEM, "can't allocate PCM object"); return &bad_pcm; + } /* Default to hw_ops, attemp plugin open only if hw (/dev/snd/pcm*) open fails */ pcm->ops = &hw_ops; @@ -1055,7 +1066,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, pcm->snd_node = snd_utils_open_pcm(card, device); pcm_type = snd_utils_get_node_type(pcm->snd_node); if (!pcm->snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) { - oops(pcm, -ENODEV, "no device (hw/plugin) for card(%u), device(%u)", + oops(&bad_pcm, ENODEV, "no device (hw/plugin) for card(%u), device(%u)", card, device); goto fail_close_dev_node; } @@ -1064,7 +1075,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, } #endif if (pcm->fd < 0) { - oops(pcm, errno, "cannot open device (%u) for card (%u)", + oops(&bad_pcm, errno, "cannot open device (%u) for card (%u)", device, card); goto fail_close_dev_node; } @@ -1072,7 +1083,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, pcm->flags = flags; if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) { - oops(pcm, errno, "cannot get info"); + oops(&bad_pcm, errno, "cannot get info"); goto fail_close; } pcm->subdevice = info.subdevice; @@ -1082,7 +1093,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, rc = pcm_hw_mmap_status(pcm); if (rc < 0) { - oops(pcm, errno, "mmap status failed"); + oops(&bad_pcm, errno, "mmap status failed"); goto fail; } @@ -1091,16 +1102,12 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC; rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg); if (rc < 0) { - oops(pcm, errno, "cannot set timestamp type"); + oops(&bad_pcm, errno, "cannot set timestamp type"); goto fail; } } #endif - /* prepare here so the user does not need to do this later */ - if (pcm_prepare(pcm)) - goto fail; - pcm->xruns = 0; return pcm; @@ -1238,6 +1245,7 @@ static inline int pcm_mmap_capture_avail(struct pcm *pcm) int pcm_mmap_avail(struct pcm *pcm) { + pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC); if (pcm->flags & PCM_IN) return pcm_mmap_capture_avail(pcm); else @@ -1403,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; @@ -1546,18 +1555,31 @@ int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames) int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count) { if ((~pcm->flags) & (PCM_OUT | PCM_MMAP)) - return -ENOSYS; + return -EINVAL; + + unsigned int frames = pcm_bytes_to_frames(pcm, count); + int res = pcm_writei(pcm, (void *) data, frames); - return pcm_mmap_transfer(pcm, (void *)data, - pcm_bytes_to_frames(pcm, count)); + if (res < 0) { + return res; + } + + return (unsigned int) res == frames ? 0 : -EIO; } int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count) { if ((~pcm->flags) & (PCM_IN | PCM_MMAP)) - return -ENOSYS; + return -EINVAL; + + unsigned int frames = pcm_bytes_to_frames(pcm, count); + int res = pcm_readi(pcm, data, frames); - return pcm_mmap_transfer(pcm, data, pcm_bytes_to_frames(pcm, count)); + if (res < 0) { + return res; + } + + return (unsigned int) res == frames ? 0 : -EIO; } /* Returns current read/write position in the mmap buffer with associated time stamp. */ @@ -1623,6 +1645,10 @@ static int pcm_generic_transfer(struct pcm *pcm, void *data, if (frames > INT_MAX) return -EINVAL; + if (pcm_state(pcm) == PCM_STATE_SETUP && pcm_prepare(pcm) != 0) { + return -1; + } + again: if (pcm->flags & PCM_MMAP) diff --git a/tests/src/mixer_test.cc b/tests/src/mixer_test.cc index 717269c..903fb8f 100644 --- a/tests/src/mixer_test.cc +++ b/tests/src/mixer_test.cc @@ -288,7 +288,7 @@ TEST_P(MixerControlsTest, Event) { EXPECT_EQ(mixer_wait_event(mixer_object, 1000), 1); - EXPECT_EQ(mixer_consume_event(mixer_object), 0); + EXPECT_EQ(mixer_consume_event(mixer_object), 1); thread.join(); ASSERT_EQ(mixer_subscribe_events(mixer_object, 0), 0); diff --git a/tests/src/pcm_in_test.cc b/tests/src/pcm_in_test.cc index e912abb..4d8a697 100644 --- a/tests/src/pcm_in_test.cc +++ b/tests/src/pcm_in_test.cc @@ -73,6 +73,7 @@ class PcmInTest : public ::testing::Test { }; TEST_F(PcmInTest, GetDelay) { + pcm_prepare(pcm_object); long delay = pcm_get_delay(pcm_object); std::cout << delay << std::endl; ASSERT_GE(delay, 0); 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_out_test.cc b/tests/src/pcm_out_test.cc index cbc6983..f8d4345 100644 --- a/tests/src/pcm_out_test.cc +++ b/tests/src/pcm_out_test.cc @@ -192,13 +192,13 @@ TEST_F(PcmOutMmapTest, Write) { buffer[i] = static_cast<char>(i); } - int written_frames = 0; + int res = 0; unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size); pcm_start(pcm_object); auto start = std::chrono::steady_clock::now(); for (uint32_t i = 0; i < write_count; ++i) { - written_frames = pcm_mmap_write(pcm_object, buffer.get(), buffer_size); - ASSERT_EQ(written_frames, frames); + res = pcm_mmap_write(pcm_object, buffer.get(), buffer_size); + ASSERT_EQ(res, 0); } pcm_stop(pcm_object); 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/tinymix.c b/utils/tinymix.c index 5c0378c..e272ade 100644 --- a/utils/tinymix.c +++ b/utils/tinymix.c @@ -370,7 +370,7 @@ fail: static int is_int(const char *value) { - return value[0] >= '0' || value[0] <= '9'; + return value[0] >= '0' && value[0] <= '9'; } struct parsed_int diff --git a/utils/tinyplay.c b/utils/tinyplay.c index 2689158..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,18 @@ 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; case 'h': print_usage(argv[0]); return EXIT_SUCCESS; @@ -326,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); @@ -396,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); @@ -418,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); |