mirror of https://github.com/Genymobile/scrcpy
Replace locks by atomics in audio player
The audio output thread only reads samples from the buffer, and most of the time, the audio receiver thread only writes samples to the buffer. In these cases, using atomics avoids lock contention. There are still corner cases where the audio receiver thread needs to "read" samples (and drop them), so lock only in these cases.audio_player_atomic.15
parent
1ea9e8f693
commit
6a01c39aac
@ -0,0 +1,112 @@
|
|||||||
|
#include "audiobuf.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <util/log.h>
|
||||||
|
#include <util/memory.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
|
||||||
|
uint32_t capacity) {
|
||||||
|
assert(sample_size);
|
||||||
|
assert(capacity);
|
||||||
|
|
||||||
|
// The actual capacity is (alloc_size - 1) so that head == tail is
|
||||||
|
// non-ambiguous
|
||||||
|
buf->alloc_size = capacity + 1;
|
||||||
|
buf->data = sc_allocarray(buf->alloc_size, sample_size);
|
||||||
|
if (!buf->data) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->sample_size = sample_size;
|
||||||
|
atomic_init(&buf->head, 0);
|
||||||
|
atomic_init(&buf->tail, 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_audiobuf_destroy(struct sc_audiobuf *buf) {
|
||||||
|
free(buf->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
|
||||||
|
assert(samples_count);
|
||||||
|
|
||||||
|
uint8_t *to = to_;
|
||||||
|
|
||||||
|
// Only the reader thread can write tail without synchronization, so
|
||||||
|
// memory_order_relaxed is sufficient
|
||||||
|
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_relaxed);
|
||||||
|
|
||||||
|
// The head cursor is updated after the data is written to the array
|
||||||
|
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
||||||
|
|
||||||
|
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||||
|
if (samples_count > can_read) {
|
||||||
|
samples_count = can_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
uint32_t right_count = buf->alloc_size - tail;
|
||||||
|
if (right_count > samples_count) {
|
||||||
|
right_count = samples_count;
|
||||||
|
}
|
||||||
|
memcpy(to,
|
||||||
|
buf->data + (tail * buf->sample_size),
|
||||||
|
right_count * buf->sample_size);
|
||||||
|
|
||||||
|
if (samples_count > right_count) {
|
||||||
|
uint32_t left_count = samples_count - right_count;
|
||||||
|
memcpy(to + (right_count * buf->sample_size),
|
||||||
|
buf->data,
|
||||||
|
left_count * buf->sample_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t new_tail = (tail + samples_count) % buf->alloc_size;
|
||||||
|
atomic_store_explicit(&buf->tail, new_tail, memory_order_release);
|
||||||
|
|
||||||
|
return samples_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
|
||||||
|
uint32_t samples_count) {
|
||||||
|
const uint8_t *from = from_;
|
||||||
|
|
||||||
|
// Only the writer thread can write head, so memory_order_relaxed is
|
||||||
|
// sufficient
|
||||||
|
uint32_t head = atomic_load_explicit(&buf->head, memory_order_relaxed);
|
||||||
|
|
||||||
|
// The tail cursor is updated after the data is consumed by the reader
|
||||||
|
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||||
|
|
||||||
|
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
||||||
|
if (samples_count > can_write) {
|
||||||
|
samples_count = can_write;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t right_count = buf->alloc_size - head;
|
||||||
|
if (right_count > samples_count) {
|
||||||
|
right_count = samples_count;
|
||||||
|
}
|
||||||
|
memcpy(buf->data + (head * buf->sample_size),
|
||||||
|
from,
|
||||||
|
right_count * buf->sample_size);
|
||||||
|
|
||||||
|
if (samples_count > right_count) {
|
||||||
|
uint32_t left_count = samples_count - right_count;
|
||||||
|
memcpy(buf->data,
|
||||||
|
from + (right_count * buf->sample_size),
|
||||||
|
left_count * buf->sample_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t new_head = (head + samples_count) % buf->alloc_size;
|
||||||
|
atomic_store_explicit(&buf->head, new_head, memory_order_release);
|
||||||
|
|
||||||
|
return samples_count;
|
||||||
|
}
|
@ -1,104 +0,0 @@
|
|||||||
#include "bytebuf.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) {
|
|
||||||
assert(alloc_size);
|
|
||||||
buf->data = malloc(alloc_size);
|
|
||||||
if (!buf->data) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->alloc_size = alloc_size;
|
|
||||||
buf->head = 0;
|
|
||||||
buf->tail = 0;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_destroy(struct sc_bytebuf *buf) {
|
|
||||||
free(buf->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_read(buf));
|
|
||||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
|
||||||
|
|
||||||
size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size;
|
|
||||||
size_t right_len = right_limit - buf->tail;
|
|
||||||
if (len < right_len) {
|
|
||||||
right_len = len;
|
|
||||||
}
|
|
||||||
memcpy(to, buf->data + buf->tail, right_len);
|
|
||||||
|
|
||||||
if (len > right_len) {
|
|
||||||
memcpy(to + right_len, buf->data, len - right_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_read(buf));
|
|
||||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
|
||||||
|
|
||||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_bytebuf_write_step0(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len) {
|
|
||||||
size_t right_len = buf->alloc_size - buf->head;
|
|
||||||
if (len < right_len) {
|
|
||||||
right_len = len;
|
|
||||||
}
|
|
||||||
memcpy(buf->data + buf->head, from, right_len);
|
|
||||||
|
|
||||||
if (len > right_len) {
|
|
||||||
memcpy(buf->data, from + right_len, len - right_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_bytebuf_write_step1(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
buf->head = (buf->head + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_write(buf));
|
|
||||||
|
|
||||||
sc_bytebuf_write_step0(buf, from, len);
|
|
||||||
sc_bytebuf_write_step1(buf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len) {
|
|
||||||
// *This function MUST NOT access buf->tail (even in assert()).*
|
|
||||||
// The purpose of this function is to allow a reader and a writer to access
|
|
||||||
// different parts of the buffer in parallel simultaneously. It is intended
|
|
||||||
// to be called without lock (only sc_bytebuf_commit_write() is intended to
|
|
||||||
// be called with lock held).
|
|
||||||
|
|
||||||
assert(len < buf->alloc_size - 1);
|
|
||||||
sc_bytebuf_write_step0(buf, from, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
assert(len <= sc_bytebuf_can_write(buf));
|
|
||||||
sc_bytebuf_write_step1(buf, len);
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
#ifndef SC_BYTEBUF_H
|
|
||||||
#define SC_BYTEBUF_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
struct sc_bytebuf {
|
|
||||||
uint8_t *data;
|
|
||||||
// The actual capacity is (allocated - 1) so that head == tail is
|
|
||||||
// non-ambiguous
|
|
||||||
size_t alloc_size;
|
|
||||||
size_t head; // writter cursor
|
|
||||||
size_t tail; // reader cursor
|
|
||||||
// empty: tail == head
|
|
||||||
// full: ((tail + 1) % alloc_size) == head
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy from the bytebuf to a user-provided array
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_read_available() (it is an
|
|
||||||
* error to attempt to read more bytes than available).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->head.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop len bytes from the buffer
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_read_available() (it is an
|
|
||||||
* error to attempt to skip more bytes than available).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->head.
|
|
||||||
*
|
|
||||||
* It is equivalent to call sc_bytebuf_read() to some array and discard the
|
|
||||||
* array (but this function is more efficient since there is no copy).
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the user-provided array to the bytebuf
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_write_available() (it is an
|
|
||||||
* error to write more bytes than the remaining available space).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->tail.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the user-provided array to the bytebuf, but do not advance the cursor
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_write_available() (it is an
|
|
||||||
* error to write more bytes than the remaining available space).
|
|
||||||
*
|
|
||||||
* After this function is called, the write must be committed with
|
|
||||||
* sc_bytebuf_commit_write().
|
|
||||||
*
|
|
||||||
* The purpose of this mechanism is to acquire a lock only to commit the write,
|
|
||||||
* but not to perform the actual copy.
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to access buf->tail.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit a prepared write
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes which can be read
|
|
||||||
*
|
|
||||||
* It is an error to read more bytes than available.
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_can_read(struct sc_bytebuf *buf) {
|
|
||||||
return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes which can be written
|
|
||||||
*
|
|
||||||
* It is an error to write more bytes than available.
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_can_write(struct sc_bytebuf *buf) {
|
|
||||||
return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the actual capacity of the buffer (can_read() + can_write())
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_capacity(struct sc_bytebuf *buf) {
|
|
||||||
return buf->alloc_size - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_destroy(struct sc_bytebuf *buf);
|
|
||||||
|
|
||||||
#endif
|
|
@ -0,0 +1,128 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/audiobuf.h"
|
||||||
|
|
||||||
|
static void test_audiobuf_simple(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 5);
|
||||||
|
assert(w == 5);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 4);
|
||||||
|
assert(r == 4);
|
||||||
|
assert(!memcmp(data, samples, 16));
|
||||||
|
|
||||||
|
uint32_t samples2[] = {6, 7, 8};
|
||||||
|
w = sc_audiobuf_write(&buf, samples2, 3);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
uint32_t single = 9;
|
||||||
|
w = sc_audiobuf_write(&buf, &single, 1);
|
||||||
|
assert(w == 1);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, &data[4], 8);
|
||||||
|
assert(r == 5);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||||
|
assert(!memcmp(data, expected, 36));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_audiobuf_boundaries(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 9);
|
||||||
|
assert(r == 9);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected, 36));
|
||||||
|
|
||||||
|
uint32_t samples2[] = {7, 8, 9, 10, 11};
|
||||||
|
w = sc_audiobuf_write(&buf, samples2, 5);
|
||||||
|
assert(w == 5);
|
||||||
|
|
||||||
|
uint32_t single = 12;
|
||||||
|
w = sc_audiobuf_write(&buf, &single, 1);
|
||||||
|
assert(w == 1);
|
||||||
|
|
||||||
|
w = sc_audiobuf_read(&buf, NULL, 3);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
assert(sc_audiobuf_can_read(&buf) == 12);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, data, 12);
|
||||||
|
assert(r == 12);
|
||||||
|
|
||||||
|
uint32_t expected2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||||
|
assert(!memcmp(data, expected2, 48));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_audiobuf_partial_read_write(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[15];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 10);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 4);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 0);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 3);
|
||||||
|
assert(r == 3);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected, 12));
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, data, 15);
|
||||||
|
assert(r == 10);
|
||||||
|
uint32_t expected2[] = {4, 5, 6, 1, 2, 3, 4, 1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected2, 12));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_audiobuf_simple();
|
||||||
|
test_audiobuf_boundaries();
|
||||||
|
test_audiobuf_partial_read_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,126 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/bytebuf.h"
|
|
||||||
|
|
||||||
static void test_bytebuf_simple(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 5);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 4);
|
|
||||||
assert(!strncmp((char *) data, "hell", 4));
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 7);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 8);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, &data[4], 8);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_bytebuf_boundaries(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 6);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 18);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 9);
|
|
||||||
assert(!strncmp((char *) data, "hello hel", 9));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 14);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 15);
|
|
||||||
|
|
||||||
sc_bytebuf_skip(&buf, 3);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 12);
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_bytebuf_two_steps_write(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 6);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12); // write not committed yet
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 9);
|
|
||||||
assert(!strncmp((char *) data, "hello hel", 3));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 3);
|
|
||||||
|
|
||||||
sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9);
|
|
||||||
|
|
||||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9); // write not committed yet
|
|
||||||
|
|
||||||
sc_bytebuf_commit_write(&buf, sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 14);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 15);
|
|
||||||
|
|
||||||
sc_bytebuf_skip(&buf, 3);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 12);
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_bytebuf_simple();
|
|
||||||
test_bytebuf_boundaries();
|
|
||||||
test_bytebuf_two_steps_write();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in New Issue