/* * 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 #include #include #include 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(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() { size_t written = 0; const int buf_sz = 16 * 1024; uint8_t buf[buf_sz]; while (int rv = ringbuffer.dequeue(buf, buf_sz)) { written += fwrite(buf, rv, 1, file); } return written; } int dump(void * samples, uint32_t count) { int bytes = static_cast(count * sample_size); int rv = ringbuffer.enqueue(static_cast(samples), bytes); return rv == bytes; } private: uint32_t sample_size; FILE * file{}; lock_free_queue 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::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(&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 streams{}; std::atomic 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; }