Source code
Revision control
Copy as Markdown
Other Tools
/*
* 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()
{
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<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;
}