cubeb_audio_dump.cpp (5627B)
1 /* 2 * Copyright © 2023 Mozilla Foundation 3 * 4 * This program is made available under an ISC-style license. See the 5 * accompanying file LICENSE for details. 6 */ 7 8 #define NOMINMAX 9 10 #include "cubeb_audio_dump.h" 11 #include "cubeb/cubeb.h" 12 #include "cubeb_ringbuffer.h" 13 #include <chrono> 14 #include <limits> 15 #include <thread> 16 #include <vector> 17 18 using std::thread; 19 using std::vector; 20 21 uint32_t 22 bytes_per_sample(cubeb_stream_params params) 23 { 24 switch (params.format) { 25 case CUBEB_SAMPLE_S16LE: 26 case CUBEB_SAMPLE_S16BE: 27 return sizeof(int16_t); 28 case CUBEB_SAMPLE_FLOAT32LE: 29 case CUBEB_SAMPLE_FLOAT32BE: 30 return sizeof(float); 31 }; 32 } 33 34 struct cubeb_audio_dump_stream { 35 public: 36 explicit cubeb_audio_dump_stream(cubeb_stream_params params) 37 : sample_size(bytes_per_sample(params)), 38 ringbuffer( 39 static_cast<int>(params.rate * params.channels * sample_size)) 40 { 41 } 42 43 int open(const char * name) 44 { 45 file = fopen(name, "wb"); 46 if (!file) { 47 return CUBEB_ERROR; 48 } 49 return CUBEB_OK; 50 } 51 int close() 52 { 53 if (fclose(file)) { 54 return CUBEB_ERROR; 55 } 56 return CUBEB_OK; 57 } 58 59 // Directly write to the file. Useful to write the header. 60 size_t write(uint8_t * data, uint32_t count) 61 { 62 return fwrite(data, count, 1, file); 63 } 64 65 size_t write_all() 66 { 67 size_t written = 0; 68 const int buf_sz = 16 * 1024; 69 uint8_t buf[buf_sz]; 70 while (int rv = ringbuffer.dequeue(buf, buf_sz)) { 71 written += fwrite(buf, rv, 1, file); 72 } 73 return written; 74 } 75 int dump(void * samples, uint32_t count) 76 { 77 int bytes = static_cast<int>(count * sample_size); 78 int rv = ringbuffer.enqueue(static_cast<uint8_t *>(samples), bytes); 79 return rv == bytes; 80 } 81 82 private: 83 uint32_t sample_size; 84 FILE * file{}; 85 lock_free_queue<uint8_t> ringbuffer; 86 }; 87 88 struct cubeb_audio_dump_session { 89 public: 90 cubeb_audio_dump_session() = default; 91 ~cubeb_audio_dump_session() 92 { 93 assert(streams.empty()); 94 session_thread.join(); 95 } 96 cubeb_audio_dump_session(const cubeb_audio_dump_session &) = delete; 97 cubeb_audio_dump_session & 98 operator=(const cubeb_audio_dump_session &) = delete; 99 cubeb_audio_dump_session & operator=(cubeb_audio_dump_session &&) = delete; 100 101 cubeb_audio_dump_stream_t create_stream(cubeb_stream_params params, 102 const char * name) 103 { 104 if (running) { 105 return nullptr; 106 } 107 auto * stream = new cubeb_audio_dump_stream(params); 108 streams.push_back(stream); 109 int rv = stream->open(name); 110 if (rv != CUBEB_OK) { 111 delete stream; 112 return nullptr; 113 } 114 115 struct riff_header { 116 char chunk_id[4] = {'R', 'I', 'F', 'F'}; 117 int32_t chunk_size = 0; 118 char format[4] = {'W', 'A', 'V', 'E'}; 119 120 char subchunk_id_1[4] = {'f', 'm', 't', 0x20}; 121 int32_t subchunk_1_size = 16; 122 int16_t audio_format{}; 123 int16_t num_channels{}; 124 int32_t sample_rate{}; 125 int32_t byte_rate{}; 126 int16_t block_align{}; 127 int16_t bits_per_sample{}; 128 129 char subchunk_id_2[4] = {'d', 'a', 't', 'a'}; 130 int32_t subchunkd_2_size = std::numeric_limits<int32_t>::max(); 131 }; 132 133 riff_header header; 134 // 1 is integer PCM, 3 is float PCM 135 header.audio_format = bytes_per_sample(params) == 2 ? 1 : 3; 136 header.num_channels = params.channels; 137 header.sample_rate = params.rate; 138 header.byte_rate = bytes_per_sample(params) * params.rate * params.channels; 139 header.block_align = params.channels * bytes_per_sample(params); 140 header.bits_per_sample = bytes_per_sample(params) * 8; 141 142 stream->write(reinterpret_cast<uint8_t *>(&header), sizeof(riff_header)); 143 144 return stream; 145 } 146 int delete_stream(cubeb_audio_dump_stream * stream) 147 { 148 assert(!running); 149 stream->close(); 150 streams.erase(std::remove(streams.begin(), streams.end(), stream), 151 streams.end()); 152 delete stream; 153 return CUBEB_OK; 154 } 155 int start() 156 { 157 assert(!running); 158 running = true; 159 session_thread = std::thread([this] { 160 while (running) { 161 for (auto * stream : streams) { 162 stream->write_all(); 163 } 164 const int DUMP_INTERVAL = 10; 165 std::this_thread::sleep_for(std::chrono::milliseconds(DUMP_INTERVAL)); 166 } 167 }); 168 return CUBEB_OK; 169 } 170 int stop() 171 { 172 assert(running); 173 running = false; 174 return CUBEB_OK; 175 } 176 177 private: 178 thread session_thread; 179 vector<cubeb_audio_dump_stream_t> streams{}; 180 std::atomic<bool> running = false; 181 }; 182 183 int 184 cubeb_audio_dump_init(cubeb_audio_dump_session_t * session) 185 { 186 *session = new cubeb_audio_dump_session; 187 return CUBEB_OK; 188 } 189 190 int 191 cubeb_audio_dump_shutdown(cubeb_audio_dump_session_t session) 192 { 193 delete session; 194 return CUBEB_OK; 195 } 196 197 int 198 cubeb_audio_dump_stream_init(cubeb_audio_dump_session_t session, 199 cubeb_audio_dump_stream_t * stream, 200 cubeb_stream_params stream_params, 201 const char * name) 202 { 203 *stream = session->create_stream(stream_params, name); 204 return CUBEB_OK; 205 } 206 207 int 208 cubeb_audio_dump_stream_shutdown(cubeb_audio_dump_session_t session, 209 cubeb_audio_dump_stream_t stream) 210 { 211 return session->delete_stream(stream); 212 } 213 214 int 215 cubeb_audio_dump_start(cubeb_audio_dump_session_t session) 216 { 217 return session->start(); 218 } 219 220 int 221 cubeb_audio_dump_stop(cubeb_audio_dump_session_t session) 222 { 223 return session->stop(); 224 } 225 226 int 227 cubeb_audio_dump_write(cubeb_audio_dump_stream_t stream, void * audio_samples, 228 uint32_t count) 229 { 230 stream->dump(audio_samples, count); 231 return CUBEB_OK; 232 }