aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/cubeb_audio_dump.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/cubeb_audio_dump.cpp')
-rw-r--r--src/cubeb_audio_dump.cpp234
1 files changed, 234 insertions, 0 deletions
diff --git a/src/cubeb_audio_dump.cpp b/src/cubeb_audio_dump.cpp
new file mode 100644
index 0000000..8de3d88
--- /dev/null
+++ b/src/cubeb_audio_dump.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright © 2023 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#define NOMINMAX
+
+#include "cubeb_audio_dump.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_ringbuffer.h"
+#include <chrono>
+#include <limits>
+#include <thread>
+#include <vector>
+
+using std::thread;
+using std::vector;
+
+uint32_t
+bytes_per_sample(cubeb_stream_params params)
+{
+ switch (params.format) {
+ case CUBEB_SAMPLE_S16LE:
+ case CUBEB_SAMPLE_S16BE:
+ return sizeof(int16_t);
+ case CUBEB_SAMPLE_FLOAT32LE:
+ case CUBEB_SAMPLE_FLOAT32BE:
+ return sizeof(float);
+ };
+}
+
+struct cubeb_audio_dump_stream {
+public:
+ explicit cubeb_audio_dump_stream(cubeb_stream_params params)
+ : sample_size(bytes_per_sample(params)),
+ ringbuffer(
+ static_cast<int>(params.rate * params.channels * sample_size))
+ {
+ }
+
+ int open(const char * name)
+ {
+ file = fopen(name, "wb");
+ if (!file) {
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+ }
+ int close()
+ {
+ if (fclose(file)) {
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+ }
+
+ // Directly write to the file. Useful to write the header.
+ size_t write(uint8_t * data, uint32_t count)
+ {
+ return fwrite(data, count, 1, file);
+ }
+
+ size_t write_all()
+ {
+ int available = ringbuffer.available_read();
+ size_t written = 0;
+ while (available) {
+ const int buf_sz = 16 * 1024;
+ uint8_t buf[buf_sz];
+ int rv = ringbuffer.dequeue(buf, buf_sz);
+ available -= rv;
+ written += fwrite(buf, rv, 1, file);
+ }
+ return written;
+ }
+ int dump(void * samples, uint32_t count)
+ {
+ int bytes = static_cast<int>(count * sample_size);
+ int rv = ringbuffer.enqueue(static_cast<uint8_t *>(samples), bytes);
+ return rv == bytes;
+ }
+
+private:
+ uint32_t sample_size;
+ FILE * file{};
+ lock_free_queue<uint8_t> ringbuffer;
+};
+
+struct cubeb_audio_dump_session {
+public:
+ cubeb_audio_dump_session() = default;
+ ~cubeb_audio_dump_session()
+ {
+ assert(streams.empty());
+ session_thread.join();
+ }
+ cubeb_audio_dump_session(const cubeb_audio_dump_session &) = delete;
+ cubeb_audio_dump_session &
+ operator=(const cubeb_audio_dump_session &) = delete;
+ cubeb_audio_dump_session & operator=(cubeb_audio_dump_session &&) = delete;
+
+ cubeb_audio_dump_stream_t create_stream(cubeb_stream_params params,
+ const char * name)
+ {
+ if (running) {
+ return nullptr;
+ }
+ auto * stream = new cubeb_audio_dump_stream(params);
+ streams.push_back(stream);
+ int rv = stream->open(name);
+ if (rv != CUBEB_OK) {
+ delete stream;
+ return nullptr;
+ }
+
+ struct riff_header {
+ char chunk_id[4] = {'R', 'I', 'F', 'F'};
+ int32_t chunk_size = 0;
+ char format[4] = {'W', 'A', 'V', 'E'};
+
+ char subchunk_id_1[4] = {'f', 'm', 't', 0x20};
+ int32_t subchunk_1_size = 16;
+ int16_t audio_format{};
+ int16_t num_channels{};
+ int32_t sample_rate{};
+ int32_t byte_rate{};
+ int16_t block_align{};
+ int16_t bits_per_sample{};
+
+ char subchunk_id_2[4] = {'d', 'a', 't', 'a'};
+ int32_t subchunkd_2_size = std::numeric_limits<int32_t>::max();
+ };
+
+ riff_header header;
+ // 1 is integer PCM, 3 is float PCM
+ header.audio_format = bytes_per_sample(params) == 2 ? 1 : 3;
+ header.num_channels = params.channels;
+ header.sample_rate = params.rate;
+ header.byte_rate = bytes_per_sample(params) * params.rate * params.channels;
+ header.block_align = params.channels * bytes_per_sample(params);
+ header.bits_per_sample = bytes_per_sample(params) * 8;
+
+ stream->write(reinterpret_cast<uint8_t *>(&header), sizeof(riff_header));
+
+ return stream;
+ }
+ int delete_stream(cubeb_audio_dump_stream * stream)
+ {
+ assert(!running);
+ stream->close();
+ streams.erase(std::remove(streams.begin(), streams.end(), stream),
+ streams.end());
+ return CUBEB_OK;
+ }
+ int start()
+ {
+ assert(!running);
+ running = true;
+ session_thread = std::thread([this] {
+ while (running) {
+ for (auto * stream : streams) {
+ stream->write_all();
+ }
+ const int DUMP_INTERVAL = 10;
+ std::this_thread::sleep_for(std::chrono::milliseconds(DUMP_INTERVAL));
+ }
+ });
+ return CUBEB_OK;
+ }
+ int stop()
+ {
+ assert(running);
+ running = false;
+ return CUBEB_OK;
+ }
+
+private:
+ thread session_thread;
+ vector<cubeb_audio_dump_stream_t> streams{};
+ std::atomic<bool> running = false;
+};
+
+int
+cubeb_audio_dump_init(cubeb_audio_dump_session_t * session)
+{
+ *session = new cubeb_audio_dump_session;
+ return CUBEB_OK;
+}
+
+int
+cubeb_audio_dump_shutdown(cubeb_audio_dump_session_t session)
+{
+ delete session;
+ return CUBEB_OK;
+}
+
+int
+cubeb_audio_dump_stream_init(cubeb_audio_dump_session_t session,
+ cubeb_audio_dump_stream_t * stream,
+ cubeb_stream_params stream_params,
+ const char * name)
+{
+ *stream = session->create_stream(stream_params, name);
+ return CUBEB_OK;
+}
+
+int
+cubeb_audio_dump_stream_shutdown(cubeb_audio_dump_session_t session,
+ cubeb_audio_dump_stream_t stream)
+{
+ return session->delete_stream(stream);
+}
+
+int
+cubeb_audio_dump_start(cubeb_audio_dump_session_t session)
+{
+ return session->start();
+}
+
+int
+cubeb_audio_dump_stop(cubeb_audio_dump_session_t session)
+{
+ return session->stop();
+}
+
+int
+cubeb_audio_dump_write(cubeb_audio_dump_stream_t stream, void * audio_samples,
+ uint32_t count)
+{
+ stream->dump(audio_samples, count);
+ return CUBEB_OK;
+}