mirror of https://github.com/nostar/urfd.git
parent
ce2e9025e9
commit
cbfaf8c176
@ -0,0 +1,229 @@
|
||||
#include "AudioRecorder.h"
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
|
||||
// Opus settings for Voice 8kHz Mono
|
||||
#define SAMPLE_RATE 8000
|
||||
#define CHANNELS 1
|
||||
#define APPLICATION OPUS_APPLICATION_VOIP
|
||||
// 60ms frame size = 480 samples at 8kHz
|
||||
#define FRAME_SIZE 480
|
||||
|
||||
CAudioRecorder::CAudioRecorder() : m_IsRecording(false), m_Encoder(nullptr), m_PacketCount(0), m_GranulePos(0)
|
||||
{
|
||||
}
|
||||
|
||||
CAudioRecorder::~CAudioRecorder()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void CAudioRecorder::Cleanup()
|
||||
{
|
||||
if (m_Encoder) {
|
||||
opus_encoder_destroy(m_Encoder);
|
||||
m_Encoder = nullptr;
|
||||
}
|
||||
if (m_IsRecording) {
|
||||
ogg_stream_clear(&m_OggStream);
|
||||
}
|
||||
if (m_File.is_open()) {
|
||||
m_File.close();
|
||||
}
|
||||
m_IsRecording = false;
|
||||
m_PcmBuffer.clear();
|
||||
}
|
||||
|
||||
std::string CAudioRecorder::Start(const std::string& directory)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
Cleanup();
|
||||
|
||||
// Generate UUIDv7 Filename
|
||||
uint8_t uuid[16];
|
||||
uint8_t rand_bytes[10];
|
||||
for(int i=0; i<10; ++i) rand_bytes[i] = std::rand() & 0xFF; // Minimal entropy for now
|
||||
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
uint64_t unix_ts_ms = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
|
||||
uuidv7_generate(uuid, unix_ts_ms, rand_bytes, nullptr);
|
||||
|
||||
char uuid_str[37];
|
||||
uuidv7_to_string(uuid, uuid_str);
|
||||
|
||||
m_Filename = "hearing_" + std::string(uuid_str) + ".opus";
|
||||
if (directory.back() == '/')
|
||||
m_FullPath = directory + m_Filename;
|
||||
else
|
||||
m_FullPath = directory + "/" + m_Filename;
|
||||
|
||||
m_File.open(m_FullPath, std::ios::binary | std::ios::out);
|
||||
if (!m_File.is_open()) {
|
||||
std::cerr << "AudioRecorder: Failed to open file: " << m_FullPath << std::endl;
|
||||
return "";
|
||||
}
|
||||
|
||||
InitOpus();
|
||||
InitOgg();
|
||||
|
||||
m_IsRecording = true;
|
||||
return m_Filename;
|
||||
}
|
||||
|
||||
void CAudioRecorder::InitOpus()
|
||||
{
|
||||
int err;
|
||||
m_Encoder = opus_encoder_create(SAMPLE_RATE, CHANNELS, APPLICATION, &err);
|
||||
if (err != OPUS_OK) {
|
||||
std::cerr << "AudioRecorder: Failed to create Opus encoder: " << opus_strerror(err) << std::endl;
|
||||
}
|
||||
opus_encoder_ctl(m_Encoder, OPUS_SET_BITRATE(12000)); // 12kbps
|
||||
}
|
||||
|
||||
void CAudioRecorder::InitOgg()
|
||||
{
|
||||
// Initialize Ogg stream with random serial
|
||||
std::srand(std::time(nullptr));
|
||||
if (ogg_stream_init(&m_OggStream, std::rand()) != 0) {
|
||||
std::cerr << "AudioRecorder: Failed to init Ogg stream" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create OpusHead packet
|
||||
// Magic: "OpusHead" (8 bytes)
|
||||
// Version: 1 (1 byte)
|
||||
// Channel Count: 1 (1 byte)
|
||||
// Pre-skip: 0 (2 bytes)
|
||||
// Input Sample Rate: 8000 (4 bytes)
|
||||
// Output Gain: 0 (2 bytes)
|
||||
// Mapping Family: 0 (1 byte)
|
||||
unsigned char header[19] = {
|
||||
'O', 'p', 'u', 's', 'H', 'e', 'a', 'd',
|
||||
1,
|
||||
CHANNELS,
|
||||
0, 0,
|
||||
0x40, 0x1f, 0x00, 0x00, // 8000 little endian
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
ogg_packet header_packet;
|
||||
header_packet.packet = header;
|
||||
header_packet.bytes = 19;
|
||||
header_packet.b_o_s = 1;
|
||||
header_packet.e_o_s = 0;
|
||||
header_packet.granulepos = 0;
|
||||
header_packet.packetno = m_PacketCount++;
|
||||
|
||||
ogg_stream_packetin(&m_OggStream, &header_packet);
|
||||
WriteOggPage(true); // Flush header
|
||||
|
||||
// OpusTags (comments) - Minimal
|
||||
// Magic: "OpusTags" (8 bytes)
|
||||
// Vendor String Length (4 bytes)
|
||||
// Vendor String
|
||||
// User Comment List Length (4 bytes)
|
||||
const char* vendor = "urfd-recorder";
|
||||
uint32_t vendor_len = strlen(vendor);
|
||||
|
||||
std::vector<unsigned char> tags;
|
||||
tags.reserve(8 + 4 + vendor_len + 4);
|
||||
const char* magic = "OpusTags";
|
||||
tags.insert(tags.end(), magic, magic + 8);
|
||||
|
||||
tags.push_back(vendor_len & 0xFF);
|
||||
tags.push_back((vendor_len >> 8) & 0xFF);
|
||||
tags.push_back((vendor_len >> 16) & 0xFF);
|
||||
tags.push_back((vendor_len >> 24) & 0xFF);
|
||||
|
||||
tags.insert(tags.end(), vendor, vendor + vendor_len);
|
||||
|
||||
// 0 comments
|
||||
tags.push_back(0); tags.push_back(0); tags.push_back(0); tags.push_back(0);
|
||||
|
||||
ogg_packet tags_packet;
|
||||
tags_packet.packet = tags.data();
|
||||
tags_packet.bytes = tags.size();
|
||||
tags_packet.b_o_s = 0;
|
||||
tags_packet.e_o_s = 0;
|
||||
tags_packet.granulepos = 0;
|
||||
tags_packet.packetno = m_PacketCount++;
|
||||
|
||||
ogg_stream_packetin(&m_OggStream, &tags_packet);
|
||||
WriteOggPage(true);
|
||||
}
|
||||
|
||||
void CAudioRecorder::WriteOggPage(bool flush)
|
||||
{
|
||||
while(true) {
|
||||
int result = flush ? ogg_stream_flush(&m_OggStream, &m_OggPage) : ogg_stream_pageout(&m_OggStream, &m_OggPage);
|
||||
if (result == 0) break;
|
||||
m_File.write((const char*)m_OggPage.header, m_OggPage.header_len);
|
||||
m_File.write((const char*)m_OggPage.body, m_OggPage.body_len);
|
||||
}
|
||||
}
|
||||
|
||||
void CAudioRecorder::Write(const int16_t* samples, int count)
|
||||
{
|
||||
if (!m_IsRecording || !m_Encoder) return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
|
||||
m_PcmBuffer.insert(m_PcmBuffer.end(), samples, samples + count);
|
||||
|
||||
unsigned char out_buf[1024];
|
||||
|
||||
while (m_PcmBuffer.size() >= FRAME_SIZE) {
|
||||
int len = opus_encode(m_Encoder, m_PcmBuffer.data(), FRAME_SIZE, out_buf, sizeof(out_buf));
|
||||
if (len < 0) {
|
||||
std::cerr << "AudioRecorder: Opus encode error: " << len << std::endl;
|
||||
} else {
|
||||
m_GranulePos += FRAME_SIZE;
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = out_buf;
|
||||
packet.bytes = len;
|
||||
packet.b_o_s = 0;
|
||||
packet.e_o_s = 0;
|
||||
packet.granulepos = m_GranulePos;
|
||||
packet.packetno = m_PacketCount++;
|
||||
|
||||
ogg_stream_packetin(&m_OggStream, &packet);
|
||||
WriteOggPage();
|
||||
}
|
||||
|
||||
m_PcmBuffer.erase(m_PcmBuffer.begin(), m_PcmBuffer.begin() + FRAME_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void CAudioRecorder::Stop()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
if (!m_IsRecording) return;
|
||||
|
||||
// Flush remaining standard logic or just close
|
||||
// In strict Opus, we might want to pad and finish, but for voice logging, truncation of <60ms is acceptable.
|
||||
// Set EOS on last packet if we had one?
|
||||
// Hard to do retroactively. Simpler is just to write a empty packet with EOS.
|
||||
|
||||
/*
|
||||
unsigned char dummy[1] = {0};
|
||||
ogg_packet packet;
|
||||
packet.packet = dummy;
|
||||
packet.bytes = 0; // Empty
|
||||
packet.b_o_s = 0;
|
||||
packet.e_o_s = 1;
|
||||
packet.granulepos = m_GranulePos;
|
||||
packet.packetno = m_PacketCount++;
|
||||
ogg_stream_packetin(&m_OggStream, &packet);
|
||||
*/
|
||||
|
||||
// Actually, just flushing logic
|
||||
WriteOggPage(true);
|
||||
|
||||
Cleanup();
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <opus/opus.h>
|
||||
#include <ogg/ogg.h>
|
||||
#include "uuidv7.h"
|
||||
|
||||
class CAudioRecorder
|
||||
{
|
||||
public:
|
||||
CAudioRecorder();
|
||||
~CAudioRecorder();
|
||||
|
||||
// Starts recording to a new file.
|
||||
// Generates a UUIDv7 based filename if path is a directory,
|
||||
// or uses the provided path + generated filename.
|
||||
// Returns the filename (without path) for notification.
|
||||
std::string Start(const std::string& directory);
|
||||
|
||||
// Writes signed 16-bit PCM samples (8kHz mono)
|
||||
void Write(const int16_t* samples, int count);
|
||||
|
||||
// Stops recording and closes file.
|
||||
void Stop();
|
||||
|
||||
bool IsRecording() const { return m_IsRecording; }
|
||||
|
||||
private:
|
||||
void InitOpus();
|
||||
void InitOgg();
|
||||
void WriteOggPage(bool flush = false);
|
||||
void Cleanup();
|
||||
|
||||
bool m_IsRecording;
|
||||
std::ofstream m_File;
|
||||
std::string m_Filename;
|
||||
std::string m_FullPath;
|
||||
std::mutex m_Mutex;
|
||||
|
||||
// Opus state
|
||||
OpusEncoder* m_Encoder;
|
||||
|
||||
// Ogg state
|
||||
ogg_stream_state m_OggStream;
|
||||
ogg_page m_OggPage;
|
||||
ogg_packet m_OggPacket;
|
||||
int m_PacketCount;
|
||||
int m_GranulePos;
|
||||
|
||||
// Buffering pcm for frame size
|
||||
std::vector<int16_t> m_PcmBuffer;
|
||||
};
|
||||
@ -0,0 +1,42 @@
|
||||
#include "AudioRecorder.h"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
int main() {
|
||||
CAudioRecorder recorder;
|
||||
std::string filename = recorder.Start(".");
|
||||
std::cout << "Recording started: " << filename << std::endl;
|
||||
|
||||
if (filename.empty()) {
|
||||
std::cerr << "Failed to start recording" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Generate 5 seconds of 440Hz sine wave
|
||||
std::vector<int16_t> samples;
|
||||
int sampleRate = 8000;
|
||||
int duration = 5;
|
||||
double frequency = 440.0;
|
||||
int totalSamples = sampleRate * duration;
|
||||
|
||||
for (int i = 0; i < totalSamples; ++i) {
|
||||
double time = (double)i / sampleRate;
|
||||
int16_t sample = (int16_t)(32000.0 * std::sin(2.0 * M_PI * frequency * time));
|
||||
samples.push_back(sample);
|
||||
}
|
||||
|
||||
// Write in chunks
|
||||
int chunkSize = 160;
|
||||
for (int i = 0; i < totalSamples; i += chunkSize) {
|
||||
recorder.Write(samples.data() + i, chunkSize);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20)); // Simulate real-time
|
||||
}
|
||||
|
||||
recorder.Stop();
|
||||
std::cout << "Recording stopped." << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,307 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* uuidv7.h - Single-file C/C++ UUIDv7 Library
|
||||
*
|
||||
* @version v0.1.6
|
||||
* @author LiosK
|
||||
* @copyright Licensed under the Apache License, Version 2.0
|
||||
* @see https://github.com/LiosK/uuidv7-h
|
||||
*/
|
||||
/*
|
||||
* Copyright 2022 LiosK
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef UUIDV7_H_BAEDKYFQ
|
||||
#define UUIDV7_H_BAEDKYFQ
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @name Status codes returned by uuidv7_generate()
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that the `unix_ts_ms` passed was used because no preceding UUID was
|
||||
* specified.
|
||||
*/
|
||||
#define UUIDV7_STATUS_UNPRECEDENTED (0)
|
||||
|
||||
/**
|
||||
* Indicates that the `unix_ts_ms` passed was used because it was greater than
|
||||
* the previous one.
|
||||
*/
|
||||
#define UUIDV7_STATUS_NEW_TIMESTAMP (1)
|
||||
|
||||
/**
|
||||
* Indicates that the counter was incremented because the `unix_ts_ms` passed
|
||||
* was no greater than the previous one.
|
||||
*/
|
||||
#define UUIDV7_STATUS_COUNTER_INC (2)
|
||||
|
||||
/**
|
||||
* Indicates that the previous `unix_ts_ms` was incremented because the counter
|
||||
* reached its maximum value.
|
||||
*/
|
||||
#define UUIDV7_STATUS_TIMESTAMP_INC (3)
|
||||
|
||||
/**
|
||||
* Indicates that the monotonic order of generated UUIDs was broken because the
|
||||
* `unix_ts_ms` passed was less than the previous one by more than ten seconds.
|
||||
*/
|
||||
#define UUIDV7_STATUS_CLOCK_ROLLBACK (4)
|
||||
|
||||
/** Indicates that an invalid `unix_ts_ms` is passed. */
|
||||
#define UUIDV7_STATUS_ERR_TIMESTAMP (-1)
|
||||
|
||||
/**
|
||||
* Indicates that the attempt to increment the previous `unix_ts_ms` failed
|
||||
* because it had reached its maximum value.
|
||||
*/
|
||||
#define UUIDV7_STATUS_ERR_TIMESTAMP_OVERFLOW (-2)
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Low-level primitives
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generates a new UUIDv7 from the given Unix time, random bytes, and previous
|
||||
* UUID.
|
||||
*
|
||||
* @param uuid_out 16-byte byte array where the generated UUID is stored.
|
||||
* @param unix_ts_ms Current Unix time in milliseconds.
|
||||
* @param rand_bytes At least 10-byte byte array filled with random bytes. This
|
||||
* function consumes the leading 4 bytes or the whole 10
|
||||
* bytes per call depending on the conditions.
|
||||
* `uuidv7_status_n_rand_consumed()` maps the return value of
|
||||
* this function to the number of random bytes consumed.
|
||||
* @param uuid_prev 16-byte byte array representing the immediately preceding
|
||||
* UUID, from which the previous timestamp and counter are
|
||||
* extracted. This may be NULL if the caller does not care
|
||||
* the ascending order of UUIDs within the same timestamp.
|
||||
* This may point to the same location as `uuid_out`; this
|
||||
* function reads the value before writing.
|
||||
* @return One of the `UUIDV7_STATUS_*` codes that describe the
|
||||
* characteristics of generated UUIDs. Callers can usually
|
||||
* ignore the status unless they need to guarantee the
|
||||
* monotonic order of UUIDs or fine-tune the generation
|
||||
* process.
|
||||
*/
|
||||
static inline int8_t uuidv7_generate(uint8_t *uuid_out, uint64_t unix_ts_ms,
|
||||
const uint8_t *rand_bytes,
|
||||
const uint8_t *uuid_prev) {
|
||||
static const uint64_t MAX_TIMESTAMP = ((uint64_t)1 << 48) - 1;
|
||||
static const uint64_t MAX_COUNTER = ((uint64_t)1 << 42) - 1;
|
||||
|
||||
if (unix_ts_ms > MAX_TIMESTAMP) {
|
||||
return UUIDV7_STATUS_ERR_TIMESTAMP;
|
||||
}
|
||||
|
||||
int8_t status;
|
||||
uint64_t timestamp = 0;
|
||||
if (uuid_prev == NULL) {
|
||||
status = UUIDV7_STATUS_UNPRECEDENTED;
|
||||
timestamp = unix_ts_ms;
|
||||
} else {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
timestamp = (timestamp << 8) | uuid_prev[i];
|
||||
}
|
||||
|
||||
if (unix_ts_ms > timestamp) {
|
||||
status = UUIDV7_STATUS_NEW_TIMESTAMP;
|
||||
timestamp = unix_ts_ms;
|
||||
} else if (unix_ts_ms + 10000 < timestamp) {
|
||||
// ignore prev if clock moves back by more than ten seconds
|
||||
status = UUIDV7_STATUS_CLOCK_ROLLBACK;
|
||||
timestamp = unix_ts_ms;
|
||||
} else {
|
||||
// increment prev counter
|
||||
uint64_t counter = uuid_prev[6] & 0x0f; // skip ver
|
||||
counter = (counter << 8) | uuid_prev[7];
|
||||
counter = (counter << 6) | (uuid_prev[8] & 0x3f); // skip var
|
||||
counter = (counter << 8) | uuid_prev[9];
|
||||
counter = (counter << 8) | uuid_prev[10];
|
||||
counter = (counter << 8) | uuid_prev[11];
|
||||
|
||||
if (counter++ < MAX_COUNTER) {
|
||||
status = UUIDV7_STATUS_COUNTER_INC;
|
||||
uuid_out[6] = counter >> 38; // ver + bits 0-3
|
||||
uuid_out[7] = counter >> 30; // bits 4-11
|
||||
uuid_out[8] = counter >> 24; // var + bits 12-17
|
||||
uuid_out[9] = counter >> 16; // bits 18-25
|
||||
uuid_out[10] = counter >> 8; // bits 26-33
|
||||
uuid_out[11] = counter; // bits 34-41
|
||||
} else {
|
||||
// increment prev timestamp at counter overflow
|
||||
status = UUIDV7_STATUS_TIMESTAMP_INC;
|
||||
timestamp++;
|
||||
if (timestamp > MAX_TIMESTAMP) {
|
||||
return UUIDV7_STATUS_ERR_TIMESTAMP_OVERFLOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uuid_out[0] = timestamp >> 40;
|
||||
uuid_out[1] = timestamp >> 32;
|
||||
uuid_out[2] = timestamp >> 24;
|
||||
uuid_out[3] = timestamp >> 16;
|
||||
uuid_out[4] = timestamp >> 8;
|
||||
uuid_out[5] = timestamp;
|
||||
|
||||
for (int i = (status == UUIDV7_STATUS_COUNTER_INC) ? 12 : 6; i < 16; i++) {
|
||||
uuid_out[i] = *rand_bytes++;
|
||||
}
|
||||
|
||||
uuid_out[6] = 0x70 | (uuid_out[6] & 0x0f); // set ver
|
||||
uuid_out[8] = 0x80 | (uuid_out[8] & 0x3f); // set var
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the number of random bytes consumsed by `uuidv7_generate()` from
|
||||
* the `UUIDV7_STATUS_*` code returned.
|
||||
*
|
||||
* @param status `UUIDV7_STATUS_*` code returned by `uuidv7_generate()`.
|
||||
* @return `4` if `status` is `UUIDV7_STATUS_COUNTER_INC` or `10`
|
||||
* otherwise.
|
||||
*/
|
||||
static inline int uuidv7_status_n_rand_consumed(int8_t status) {
|
||||
return status == UUIDV7_STATUS_COUNTER_INC ? 4 : 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a UUID in the 8-4-4-4-12 hexadecimal string representation.
|
||||
*
|
||||
* @param uuid 16-byte byte array representing the UUID to encode.
|
||||
* @param string_out Character array where the encoded string is stored. Its
|
||||
* length must be 37 (36 digits + NUL) or longer.
|
||||
*/
|
||||
static inline void uuidv7_to_string(const uint8_t *uuid, char *string_out) {
|
||||
static const char DIGITS[] = "0123456789abcdef";
|
||||
for (int i = 0; i < 16; i++) {
|
||||
uint_fast8_t e = uuid[i];
|
||||
*string_out++ = DIGITS[e >> 4];
|
||||
*string_out++ = DIGITS[e & 15];
|
||||
if (i == 3 || i == 5 || i == 7 || i == 9) {
|
||||
*string_out++ = '-';
|
||||
}
|
||||
}
|
||||
*string_out = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the 8-4-4-4-12 hexadecimal string representation of a UUID.
|
||||
*
|
||||
* @param string 37-byte (36 digits + NUL) character array representing the
|
||||
* 8-4-4-4-12 hexadecimal string representation.
|
||||
* @param uuid_out 16-byte byte array where the decoded UUID is stored.
|
||||
* @return Zero on success or non-zero integer on failure.
|
||||
*/
|
||||
static inline int uuidv7_from_string(const char *string, uint8_t *uuid_out) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
char c = *string++;
|
||||
// clang-format off
|
||||
uint8_t x = c == '0' ? 0 : c == '1' ? 1 : c == '2' ? 2 : c == '3' ? 3
|
||||
: c == '4' ? 4 : c == '5' ? 5 : c == '6' ? 6 : c == '7' ? 7
|
||||
: c == '8' ? 8 : c == '9' ? 9 : c == 'a' ? 10 : c == 'b' ? 11
|
||||
: c == 'c' ? 12 : c == 'd' ? 13 : c == 'e' ? 14 : c == 'f' ? 15
|
||||
: c == 'A' ? 10 : c == 'B' ? 11 : c == 'C' ? 12 : c == 'D' ? 13
|
||||
: c == 'E' ? 14 : c == 'F' ? 15 : 0xff;
|
||||
// clang-format on
|
||||
if (x == 0xff) {
|
||||
return -1; // invalid digit
|
||||
}
|
||||
|
||||
if ((i & 1) == 0) {
|
||||
uuid_out[i >> 1] = x << 4; // even i => hi 4 bits
|
||||
} else {
|
||||
uuid_out[i >> 1] |= x; // odd i => lo 4 bits
|
||||
}
|
||||
|
||||
if ((i == 7 || i == 11 || i == 15 || i == 19) && (*string++ != '-')) {
|
||||
return -1; // invalid format
|
||||
}
|
||||
}
|
||||
if (*string != '\0') {
|
||||
return -1; // invalid length
|
||||
}
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name High-level APIs that require platform integration
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generates a new UUIDv7 with the current Unix time.
|
||||
*
|
||||
* This declaration defines the interface to generate a new UUIDv7 with the
|
||||
* current time, default random number generator, and global shared state
|
||||
* holding the previously generated UUID. Since this single-file library does
|
||||
* not provide platform-specific implementations, users need to prepare a
|
||||
* concrete implementation (if necessary) by integrating a real-time clock,
|
||||
* cryptographically strong random number generator, and shared state storage
|
||||
* available in the target platform.
|
||||
*
|
||||
* @param uuid_out 16-byte byte array where the generated UUID is stored.
|
||||
* @return One of the `UUIDV7_STATUS_*` codes that describe the
|
||||
* characteristics of generated UUIDs or an
|
||||
* implementation-dependent code. Callers can usually ignore
|
||||
* the `UUIDV7_STATUS_*` code unless they need to guarantee the
|
||||
* monotonic order of UUIDs or fine-tune the generation
|
||||
* process. The implementation-dependent code must be out of
|
||||
* the range of `int8_t` and negative if it reports an error.
|
||||
*/
|
||||
int uuidv7_new(uint8_t *uuid_out);
|
||||
|
||||
/**
|
||||
* Generates an 8-4-4-4-12 hexadecimal string representation of new UUIDv7.
|
||||
*
|
||||
* @param string_out Character array where the encoded string is stored. Its
|
||||
* length must be 37 (36 digits + NUL) or longer.
|
||||
* @return Return value of `uuidv7_new()`.
|
||||
* @note Provide a concrete `uuidv7_new()` implementation to enable
|
||||
* this function.
|
||||
*/
|
||||
static inline int uuidv7_new_string(char *string_out) {
|
||||
uint8_t uuid[16];
|
||||
int result = uuidv7_new(uuid);
|
||||
uuidv7_to_string(uuid, string_out);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" { */
|
||||
#endif
|
||||
|
||||
#endif /* #ifndef UUIDV7_H_BAEDKYFQ */
|
||||
Loading…
Reference in new issue