Commit 8ec0d0b0 authored by Andrea Gianarda's avatar Andrea Gianarda
Browse files

Refactor aaudio plugin:

- recorder is the input stream (i.e. microphone)
- player is the output stream (i.e. speaker)
- top is the top level module
Delete androidsound_aaudio.cpp
parent 721fc695
......@@ -82,14 +82,18 @@ find_package(Mediastreamer2 CONFIG REQUIRED)
find_package(bctoolbox CONFIG REQUIRED)
find_package(ortp CONFIG REQUIRED)
set(AAUDIO_INCUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include")
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${MEDIASTREAMER2_INCLUDE_DIRS}
${AAUDIO_INCUDE_DIRS}
)
set(LIBS ${MEDIASTREAMER2_LIBRARIES} aaudio ${ORTP_LIBRARIES} ${BCTOOLBOX_CORE_LIBRARIES})
set(SOURCE_FILES androidsound_aaudio.cpp)
set(SOURCE_FILES msaaudio_top.cpp msaaudio_player.cpp msaaudio_recorder.cpp)
set(MS2_PLUGINS_DIR "${MEDIASTREAMER2_PLUGINS_LOCATION}")
......
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* aaudio.h - Header file of Android Media plugin for Linphone, based on AAudio APIs.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ms_aaudio_h
#define ms_aaudio_h
#include <mediastreamer2/mssndcard.h>
static int DeviceFavoriteSampleRate = 44100;
struct AAudioContext {
AAudioContext() {
samplerate = DeviceFavoriteSampleRate;
nchannels = 1;
builtin_aec = false;
}
int samplerate;
int nchannels;
bool builtin_aec;
};
MSFilter *android_snd_card_create_reader(MSSndCard *card);
MSFilter *android_snd_card_create_writer(MSSndCard *card);
void register_aaudio_recorder(MSFactory* factory);
void register_aaudio_player(MSFactory* factory);
#endif // ms_aaudio_h
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* aaudio_player.cpp - Android Media Player plugin for Linphone, based on AAudio APIs.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <aaudio/AAudio.h>
#include <msaaudio/msaaudio.h>
static const int flowControlIntervalMs = 5000;
static const int flowControlThresholdMs = 40;
struct AAudioOutputContext {
AAudioOutputContext(MSFilter *f) {
mFilter = f;
ms_flow_controlled_bufferizer_init(&buffer, f, DeviceFavoriteSampleRate, 1);
ms_mutex_init(&mutex, NULL);
ms_mutex_init(&stream_mutex, NULL);
deviceId = AAUDIO_UNSPECIFIED;
soundCard = NULL;
usage = AAUDIO_USAGE_VOICE_COMMUNICATION;
content_type = AAUDIO_CONTENT_TYPE_SPEECH;
}
~AAudioOutputContext() {
ms_flow_controlled_bufferizer_uninit(&buffer);
ms_mutex_destroy(&mutex);
ms_mutex_destroy(&stream_mutex);
}
void setContext(AAudioContext *context) {
aaudio_context = context;
ms_flow_controlled_bufferizer_set_samplerate(&buffer, aaudio_context->samplerate);
ms_flow_controlled_bufferizer_set_nchannels(&buffer, aaudio_context->nchannels);
ms_flow_controlled_bufferizer_set_max_size_ms(&buffer, flowControlThresholdMs);
ms_flow_controlled_bufferizer_set_flow_control_interval_ms(&buffer, flowControlIntervalMs);
}
void updateStreamTypeFromMsSndCard() {
MSSndCardStreamType type = ms_snd_card_get_stream_type(soundCard);
if (type == MS_SND_CARD_STREAM_RING) {
usage = AAUDIO_USAGE_NOTIFICATION_RINGTONE;
content_type = AAUDIO_CONTENT_TYPE_SONIFICATION;
ms_message("[AAudio] Using RING mode");
} else if (type == MS_SND_CARD_STREAM_MEDIA) {
usage = AAUDIO_USAGE_MEDIA;
content_type = AAUDIO_CONTENT_TYPE_MUSIC;
ms_message("[AAudio] Using MEDIA mode");
} else if (type == MS_SND_CARD_STREAM_DTMF) {
usage = AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING;
content_type = AAUDIO_CONTENT_TYPE_SONIFICATION ;
ms_message("[AAudio] Using DTMF mode");
} else {
usage = AAUDIO_USAGE_VOICE_COMMUNICATION;
content_type = AAUDIO_CONTENT_TYPE_SPEECH;
ms_message("[AAudio] Using COMMUNICATION mode");
}
}
AAudioContext *aaudio_context;
AAudioStream *stream;
ms_mutex_t stream_mutex;
MSSndCard *soundCard;
MSFilter *mFilter;
MSFlowControlledBufferizer buffer;
int32_t samplesPerFrame;
ms_mutex_t mutex;
aaudio_usage_t usage;
aaudio_content_type_t content_type;
int32_t deviceId;
};
static void android_snd_write_init(MSFilter *obj){
AAudioOutputContext *octx = new AAudioOutputContext(obj);
obj->data = octx;
}
static void android_snd_write_uninit(MSFilter *obj){
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
delete octx;
}
static int android_snd_write_set_sample_rate(MSFilter *obj, void *data) {
return -1; /*don't accept custom sample rates, use recommended rate always*/
}
static int android_snd_write_get_sample_rate(MSFilter *obj, void *data) {
int *n = (int*)data;
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
*n = octx->aaudio_context->samplerate;
return 0;
}
static int android_snd_write_set_nchannels(MSFilter *obj, void *data) {
int *n = (int*)data;
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
octx->aaudio_context->nchannels = *n;
ms_flow_controlled_bufferizer_set_nchannels(&octx->buffer, octx->aaudio_context->nchannels);
return 0;
}
static int android_snd_write_get_nchannels(MSFilter *obj, void *data) {
int *n = (int*)data;
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
*n = octx->aaudio_context->nchannels;
return 0;
}
static aaudio_data_callback_result_t aaudio_player_callback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) {
AAudioOutputContext *octx = (AAudioOutputContext*)userData;
if (numFrames <= 0) {
ms_error("[AAudio] aaudio_player_callback has %i frames", numFrames);
}
ms_mutex_lock(&octx->mutex);
int ask = sizeof(int16_t) * numFrames * octx->samplesPerFrame;
int avail = ms_flow_controlled_bufferizer_get_avail(&octx->buffer);
int bytes = MIN(ask, avail);
if (bytes > 0) {
ms_flow_controlled_bufferizer_read(&octx->buffer, (uint8_t *)audioData, bytes);
} else if (avail < ask) {
memset(static_cast<int16_t *>(audioData) + avail, 0, ask - avail);
}
ms_mutex_unlock(&octx->mutex);
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
static void aaudio_player_callback_error(AAudioStream *stream, void *userData, aaudio_result_t error);
static void aaudio_player_init(AAudioOutputContext *octx) {
AAudioStreamBuilder *builder;
aaudio_result_t result = AAudio_createStreamBuilder(&builder);
if (result != AAUDIO_OK && !builder) {
ms_error("[AAudio] Couldn't create stream builder for player: %i / %s", result, AAudio_convertResultToText(result));
}
octx->updateStreamTypeFromMsSndCard();
AAudioStreamBuilder_setDeviceId(builder, octx->deviceId);
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
AAudioStreamBuilder_setSampleRate(builder, octx->aaudio_context->samplerate);
AAudioStreamBuilder_setDataCallback(builder, aaudio_player_callback, octx);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
AAudioStreamBuilder_setChannelCount(builder, octx->aaudio_context->nchannels);
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE); // If EXCLUSIVE mode isn't available the builder will fall back to SHARED mode.
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_setErrorCallback(builder, aaudio_player_callback_error, octx);
AAudioStreamBuilder_setUsage(builder, octx->usage); // Requires NDK build target of 28 instead of 26 !
AAudioStreamBuilder_setContentType(builder, octx->content_type); // Requires NDK build target of 28 instead of 26 !
ms_message("[AAudio] Player stream configured with samplerate %i and %i channels", octx->aaudio_context->samplerate, octx->aaudio_context->nchannels);
result = AAudioStreamBuilder_openStream(builder, &(octx->stream));
if (result != AAUDIO_OK && !octx->stream) {
ms_error("[AAudio] Open stream for player failed: %i / %s", result, AAudio_convertResultToText(result));
AAudioStreamBuilder_delete(builder);
return;
} else {
ms_message("[AAudio] Player stream opened");
}
int32_t framesPerBust = AAudioStream_getFramesPerBurst(octx->stream);
// Set the buffer size to the burst size - this will give us the minimum possible latency
AAudioStream_setBufferSizeInFrames(octx->stream, framesPerBust * octx->aaudio_context->nchannels);
octx->samplesPerFrame = AAudioStream_getSamplesPerFrame(octx->stream);
result = AAudioStream_requestStart(octx->stream);
if (result != AAUDIO_OK) {
ms_error("[AAudio] Start stream for player failed: %i / %s", result, AAudio_convertResultToText(result));
result = AAudioStream_close(octx->stream);
if (result != AAUDIO_OK) {
ms_error("[AAudio] Player stream close failed: %i / %s", result, AAudio_convertResultToText(result));
} else {
ms_message("[AAudio] Player stream closed");
}
octx->stream = NULL;
} else {
ms_message("[AAudio] Player stream started");
}
AAudioStreamBuilder_delete(builder);
}
static void aaudio_player_close(AAudioOutputContext *octx) {
ms_mutex_lock(&octx->stream_mutex);
if (octx->stream) {
aaudio_result_t result = AAudioStream_requestStop(octx->stream);
if (result != AAUDIO_OK) {
ms_error("[AAudio] Player stream stop failed: %i / %s", result, AAudio_convertResultToText(result));
} else {
ms_message("[AAudio] Player stream stopped");
}
result = AAudioStream_close(octx->stream);
if (result != AAUDIO_OK) {
ms_error("[AAudio] Player stream close failed: %i / %s", result, AAudio_convertResultToText(result));
} else {
ms_message("[AAudio] Player stream closed");
}
octx->stream = NULL;
}
ms_mutex_unlock(&octx->stream_mutex);
}
static void aaudio_player_callback_error(AAudioStream *stream, void *userData, aaudio_result_t result) {
AAudioOutputContext *octx = (AAudioOutputContext *)userData;
ms_error("[AAudio] aaudio_player_callback_error has result: %i / %s", result, AAudio_convertResultToText(result));
}
static void android_snd_write_preprocess(MSFilter *obj) {
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
aaudio_player_init(octx);
}
static void android_snd_write_process(MSFilter *obj) {
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
ms_mutex_lock(&octx->stream_mutex);
if (!octx->stream) {
aaudio_player_init(octx);
} else {
aaudio_stream_state_t streamState = AAudioStream_getState(octx->stream);
if (streamState == AAUDIO_STREAM_STATE_DISCONNECTED) {
ms_warning("[AAudio] Player stream has disconnected");
if (octx->stream) {
AAudioStream_close(octx->stream);
octx->stream = NULL;
}
}
}
ms_mutex_unlock(&octx->stream_mutex);
ms_mutex_lock(&octx->mutex);
ms_flow_controlled_bufferizer_put_from_queue(&octx->buffer, obj->inputs[0]);
ms_mutex_unlock(&octx->mutex);
}
static void android_snd_write_postprocess(MSFilter *obj) {
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
aaudio_player_close(octx);
}
static MSFilterMethod android_snd_write_methods[] = {
{MS_FILTER_SET_SAMPLE_RATE, android_snd_write_set_sample_rate},
{MS_FILTER_GET_SAMPLE_RATE, android_snd_write_get_sample_rate},
{MS_FILTER_SET_NCHANNELS, android_snd_write_set_nchannels},
{MS_FILTER_GET_NCHANNELS, android_snd_write_get_nchannels},
{0,NULL}
};
MSFilterDesc android_snd_aaudio_player_desc = {
MS_FILTER_PLUGIN_ID,
"MSAAudioPlayer",
"android sound output",
MS_FILTER_OTHER,
NULL,
1,
0,
android_snd_write_init,
android_snd_write_preprocess,
android_snd_write_process,
android_snd_write_postprocess,
android_snd_write_uninit,
android_snd_write_methods
};
// Register aaudio player to the factory
void register_aaudio_player(MSFactory* factory) {
ms_factory_register_filter(factory, &android_snd_aaudio_player_desc);
}
static MSFilter* ms_android_snd_write_new(MSFactory* factory) {
MSFilter *f = ms_factory_create_filter_from_desc(factory, &android_snd_aaudio_player_desc);
return f;
}
MSFilter *android_snd_card_create_writer(MSSndCard *card) {
MSFilter *f = ms_android_snd_write_new(ms_snd_card_get_factory(card));
AAudioOutputContext *octx = static_cast<AAudioOutputContext*>(f->data);
octx->soundCard = card;
octx->setContext((AAudioContext*)card->data);
return f;
}
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* androidsound_aaudio.cpp - Android Media plugin for Linphone, based on AAudio APIs.
* aaudio_recorder.cpp - Android Media Recorder plugin for Linphone, based on AAudio APIs.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -17,40 +17,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <mediastreamer2/msfilter.h>
#include <mediastreamer2/msjava.h>
#include <mediastreamer2/msticker.h>
#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/devices.h>
#include <mediastreamer2/android_utils.h>
#include <sys/types.h>
#include <string.h>
#include <jni.h>
#include <dlfcn.h>
#include <aaudio/AAudio.h>
static const int flowControlIntervalMs = 5000;
static const int flowControlThresholdMs = 40;
static int DeviceFavoriteSampleRate = 44100;
static MSSndCard *android_snd_card_new(MSSndCardManager *m);
static MSFilter *ms_android_snd_read_new(MSFactory *factory);
static MSFilter *ms_android_snd_write_new(MSFactory* factory);
struct AAudioContext {
AAudioContext() {
samplerate = DeviceFavoriteSampleRate;
nchannels = 1;
builtin_aec = false;
}
int samplerate;
int nchannels;
bool builtin_aec;
};
#include <msaaudio/msaaudio.h>
struct AAudioInputContext {
AAudioInputContext() {
......@@ -92,114 +64,6 @@ struct AAudioInputContext {
jobject aec;
};
struct AAudioOutputContext {
AAudioOutputContext(MSFilter *f) {
mFilter = f;
ms_flow_controlled_bufferizer_init(&buffer, f, DeviceFavoriteSampleRate, 1);
ms_mutex_init(&mutex, NULL);
ms_mutex_init(&stream_mutex, NULL);
deviceId = AAUDIO_UNSPECIFIED;
soundCard = NULL;
usage = AAUDIO_USAGE_VOICE_COMMUNICATION;
content_type = AAUDIO_CONTENT_TYPE_SPEECH;
}
~AAudioOutputContext() {
ms_flow_controlled_bufferizer_uninit(&buffer);
ms_mutex_destroy(&mutex);
ms_mutex_destroy(&stream_mutex);
}
void setContext(AAudioContext *context) {
aaudio_context = context;
ms_flow_controlled_bufferizer_set_samplerate(&buffer, aaudio_context->samplerate);
ms_flow_controlled_bufferizer_set_nchannels(&buffer, aaudio_context->nchannels);
ms_flow_controlled_bufferizer_set_max_size_ms(&buffer, flowControlThresholdMs);
ms_flow_controlled_bufferizer_set_flow_control_interval_ms(&buffer, flowControlIntervalMs);
}
void updateStreamTypeFromMsSndCard() {
MSSndCardStreamType type = ms_snd_card_get_stream_type(soundCard);
if (type == MS_SND_CARD_STREAM_RING) {
usage = AAUDIO_USAGE_NOTIFICATION_RINGTONE;
content_type = AAUDIO_CONTENT_TYPE_SONIFICATION;
ms_message("[AAudio] Using RING mode");
} else if (type == MS_SND_CARD_STREAM_MEDIA) {
usage = AAUDIO_USAGE_MEDIA;
content_type = AAUDIO_CONTENT_TYPE_MUSIC;
ms_message("[AAudio] Using MEDIA mode");
} else if (type == MS_SND_CARD_STREAM_DTMF) {
usage = AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING;
content_type = AAUDIO_CONTENT_TYPE_SONIFICATION ;
ms_message("[AAudio] Using DTMF mode");
} else {
usage = AAUDIO_USAGE_VOICE_COMMUNICATION;
content_type = AAUDIO_CONTENT_TYPE_SPEECH;
ms_message("[AAudio] Using COMMUNICATION mode");
}
}
AAudioContext *aaudio_context;
AAudioStream *stream;
ms_mutex_t stream_mutex;
MSSndCard *soundCard;
MSFilter *mFilter;
MSFlowControlledBufferizer buffer;
int32_t samplesPerFrame;
ms_mutex_t mutex;
aaudio_usage_t usage;
aaudio_content_type_t content_type;
int32_t deviceId;
};
static AAudioContext* aaudio_context_init() {
AAudioContext* ctx = new AAudioContext();
return ctx;
}
int initAAudio() {
int result = 0;
void *handle;
if ((handle = dlopen("libaaudio.so", RTLD_NOW)) == NULL){
ms_warning("[AAudio] Fail to load libAAudio : %s", dlerror());
result = -1;
} else {
dlerror(); // Clear previous message if present
AAudioStreamBuilder *builder;
aaudio_result_t result = AAudio_createStreamBuilder(&builder);
if (result != AAUDIO_OK && !builder) {
ms_error("[AAudio] Couldn't create stream builder: %i / %s", result, AAudio_convertResultToText(result));
result += 1;
}
}
return result;
}
static void android_snd_card_detect(MSSndCardManager *m) {
SoundDeviceDescription* d = NULL;
MSDevicesInfo *devices = NULL;
if (initAAudio() == 0) {
devices = ms_factory_get_devices_info(m->factory);
d = ms_devices_info_get_sound_device_description(devices);
MSSndCard *card = android_snd_card_new(m);
ms_snd_card_manager_prepend_card(m, card);
} else {
ms_warning("[AAudio] Failed to dlopen libAAudio, AAudio MS soundcard unavailable");
}
}
static void android_native_snd_card_init(MSSndCard *card) {
}
static void android_native_snd_card_uninit(MSSndCard *card) {
AAudioContext *ctx = (AAudioContext*)card->data;
ms_warning("[AAudio] Deletion of AAudio context [%p]", ctx);
}
static AAudioInputContext* aaudio_input_context_init() {
AAudioInputContext* ictx = new AAudioInputContext();
return ictx;
......@@ -433,7 +297,7 @@ static MSFilterMethod android_snd_read_methods[] = {
{0,NULL}
};
MSFilterDesc android_snd_aaudio_read_desc = {
MSFilterDesc android_snd_aaudio_recorder_desc = {
MS_FILTER_PLUGIN_ID,
"MSAAudioRecorder",
"android sound source",
......@@ -449,302 +313,21 @@ MSFilterDesc android_snd_aaudio_read_desc = {
android_snd_read_methods
};
// Register aaudio recorder to the factory
void register_aaudio_recorder(MSFactory* factory) {
ms_factory_register_filter(factory, &android_snd_aaudio_recorder_desc);
}
static MSFilter* ms_android_snd_read_new(MSFactory *factory) {
MSFilter *f = ms_factory_create_filter_from_desc(factory, &android_snd_aaudio_read_desc);
MSFilter *f = ms_factory_create_filter_from_desc(factory, &android_snd_aaudio_recorder_desc);
return f;
}
static MSFilter *android_snd_card_create_reader(MSSndCard *card) {
MSFilter *android_snd_card_create_reader(MSSndCard *card) {
MSFilter *f = ms_android_snd_read_new(ms_snd_card_get_factory(card));
AAudioInputContext *ictx = static_cast<AAudioInputContext*>(f->data);
ictx->soundCard = card;
ictx->setContext((AAudioContext*)card->data);
return f;
}
static MSFilter *android_snd_card_create_writer(MSSndCard *card) {
MSFilter *f = ms_android_snd_write_new(ms_snd_card_get_factory(card));
AAudioOutputContext *octx = static_cast<AAudioOutputContext*>(f->data);
octx->soundCard = card;
octx->setContext((AAudioContext*)card->data);
return f;
}
static void android_snd_write_init(MSFilter *obj){
AAudioOutputContext *octx = new AAudioOutputContext(obj);
obj->data = octx;
}
static void android_snd_write_uninit(MSFilter *obj){
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
delete octx;
}
static int android_snd_write_set_sample_rate(MSFilter *obj, void *data) {
return -1; /*don't accept custom sample rates, use recommended rate always*/
}
static int android_snd_write_get_sample_rate(MSFilter *obj, void *data) {
int *n = (int*)data;
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
*n = octx->aaudio_context->samplerate;
return 0;
}
static int android_snd_write_set_nchannels(MSFilter *obj, void *data) {
int *n = (int*)data;
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
octx->aaudio_context->nchannels = *n;
ms_flow_controlled_bufferizer_set_nchannels(&octx->buffer, octx->aaudio_context->nchannels);
return 0;
}
static int android_snd_write_get_nchannels(MSFilter *obj, void *data) {
int *n = (int*)data;
AAudioOutputContext *octx = (AAudioOutputContext*)obj->data;
*n = octx->aaudio_context->nchannels;
return 0;
}
static aaudio_data_callback_result_t aaudio_player_callback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) {
AAudioOutputContext *octx = (AAudioOutputContext*)userData;
if (numFrames <= 0) {
ms_error("[AAudio] aaudio_player_callback has %i frames", numFrames);
}
ms_mutex_lock(&octx->mutex);
int ask = sizeof(int16_t) * numFrames * octx->samplesPerFrame;
int avail = ms_flow_controlled_bufferizer_get_avail(&octx->buffer);
int bytes = MIN(ask, avail);
<