Как читать из буфера с обратной связью, чтобы буфер не переполнялся?

у меня есть этот код

#define BUFFER_LEN (2048)
static float buffer[BUFFER_LEN];
int readcount;

while ((readcount = sf_read_float(handle, buffer, BUFFER_LEN))) {
  // alsa play
}

который читает BUFFER_LEN поплавков из буфера и возвращает количество поплавков, которые он фактически прочитал. «handle» сообщает sf_rad_float, насколько велик буфер.

Например. если буфер содержит 5 чисел с плавающей запятой, а BUFFER_LEN равен 3, readcount сначала вернет 3, а в следующий раз 2, и цикл while завершится.

Я хотел бы иметь функцию, которая делает то же самое.

Обновить

После большого количества кодирования я думаю, что это решение.

#include <stdio.h>

int copy_buffer(double* src, int src_length, int* src_pos,
                float* dest, int dest_length) {

  int copy_length = 0;

  if (src_length - *src_pos > dest_length) {
    copy_length = dest_length;
    printf("copy_length1 %i\n", copy_length);
  } else {
    copy_length = src_length - *src_pos;
    printf("copy_length2 %i\n", copy_length);
  }

  for (int i = 0; i < copy_length; i++) {
    dest[i] = (float) src[*src_pos + i];
  }

  // remember where to continue next time the copy_buffer() is called
  *src_pos += copy_length;

  return copy_length;
}

int main() {

  double src[] = {1,2,3,4,5};
  int src_length = 5;

  float dest[] = {0,0};
  int dest_length = 2;

  int read;
  int src_pos = 0;
  read = copy_buffer(src, src_length, &src_pos, dest, dest_length);
  printf("read %i\n", read);
  printf("src_pos %i\n", src_pos);

  for (int i = 0; i < src_length; i++) {
    printf("src %f\n", src[i]);
  }

  for (int i = 0; i < dest_length; i++) {
    printf("dest %f\n", dest[i]);
  }

  return 0;

}

При следующем вызове copy_buffer() dest будет содержать 3,4. Повторный запуск copy_buffer() копирует только значение "5". Так что я думаю, что это работает сейчас.

Хотя это не очень красиво, что у меня int src_pos = 0; снаружи на copy_buffer().

Было бы намного лучше, если бы я мог дать copy_buffer() уникальный дескриптор вместо &src_pos, как это делает sndfile.

Кто-нибудь знает, как это можно сделать?


person Louise    schedule 14.02.2010    source источник
comment
Почему вы не можете просто выполнять любые операции, которые вам нужны, на src_buffer напрямую?   -  person Carl Norum    schedule 14.02.2010
comment
Прости за это. Теперь должно быть немного лучше...? Первый код показывает, как это делает sndfile, а второй код — это то, что я хотел бы сделать.   -  person Louise    schedule 14.02.2010


Ответы (2)


Если вы хотите создать уникальные дескрипторы, вы можете сделать это с помощью malloc() и struct:

typedef intptr_t HANDLE_TYPE;

HANDLE_TYPE init_buffer_traverse(double * src, size_t src_len);
int copy_buffer(HANDLE_TYPE h_traverse, double * dest, size_t dest_len);
void close_handle_buffer_traverse(HANDLE_TYPE h);

typedef struct 
{
    double * source;
    size_t source_length;
    size_t position;
} TRAVERSAL;

#define INVALID_HANDLE 0
/*
 * Returns a new traversal handle, or 0 (INVALID_HANDLE) on failure.
 *
 * Allocates memory to contain the traversal state.
 * Resets traversal state to beginning of source buffer.
 */
HANDLE_TYPE init_buffer_traverse(double *src, size_t src_len)
{
    TRAVERSAL * trav = malloc(sizeof(TRAVERSAL));

    if (NULL == trav)
        return INVALID_HANDLE;

    trav->source = src;
    trav->source_len = src_len;
    trav->position = 0;

    return (HANDLE_TYPE)trav;
}
/*
 * Returns the system resources (memory) associated with the traversal handle.
 */
void close_handle_buffer_traverse(HANDLE_TYPE h)
{
    TRAVERSAL * trav = NULL;

    if (INVALID_HANDLE != h)
        free((TRAVERSAL *)h);
}
int copy_buffer(HANDLE_TYPE h,
                float* dest, int dest_length)
{
    TRAVERSAL * trav = NULL;

    if (INVALID_HANDLE == h)
        return -1;

    trav = (TRAVERSAL *)h;

    int copy_length = trav->source_length - trav->position;
    if (dest_length < copy_length)
        copy_length = dest_length;

    for (int i = 0; i*emphasized text* < copy_length; i++)
        dest[i] = trav->source[trav->position + i];

    // remember where to continue next time the copy_buffer() is called
    trav->position += copy_length;

    return copy_length;
}

Такой стиль использовался некоторыми программистами на C до появления C++. Стиль включает в себя структуру данных, которая содержит все элементы данных нашего «класса». Большинство API для класса принимает в качестве первого аргумента указатель на одну из этих структур. Этот указатель аналогичен указателю this. В нашем примере этот параметр был назван trav.

Исключением для API будут те методы, которые выделяют тип дескриптора; они похожи на конструкторы и имеют тип дескриптора в качестве возвращаемого значения. В нашем случае имя init_buffer_traverse могло бы также называться construct_traversal_handle.

Существует множество других методов, помимо этого, для реализации значения "непрозрачного дескриптора". Фактически, некоторые кодеры манипулировали битами (например, с помощью XOR), чтобы скрыть истинную природу дескрипторов. (Эта неясность не обеспечивает безопасности там, где это необходимо.)

В приведенном примере я не уверен (не смотрел на sndlib), имеет ли смысл хранить указатель и длину буфера назначения в структуре дескриптора или нет. Если это так, это сделает его дескриптором «буфера копирования», а не дескриптором «обхода», и вы захотите изменить всю терминологию из этого ответа.

Эти дескрипторы действительны только в течение времени жизни текущего процесса, поэтому они не подходят для дескрипторов, которые должны выдерживать перезапуски сервера дескрипторов. Для этого используйте базу данных ISAM и идентификатор столбца в качестве дескриптора. Подход с базой данных намного медленнее, чем подход с памятью/указателем, но для постоянных дескрипторов вы все равно не можете использовать значения в памяти.

С другой стороны, похоже, что вы реализуете библиотеку, которая будет работать в течение одного времени жизни процесса. В этом случае ответ, который я написал, должен быть пригоден для использования после модификации в соответствии с вашими требованиями.

Дополнение

Вы просили пояснить сходство с C++, о котором я упоминал выше. Чтобы быть конкретным, некоторый эквивалент (приведенному выше коду C) код C++ может быть:

class TRAVERSAL
{
    double * source;
    size_t source_length;
    size_t position;

    public TRAVERSAL(double *src, size_t src_len)
    {
        source = src;
        source_length = src_len;
        position = 0;
    }

    public int copy_buffer(double * dest, size_t dest_len)
    {
        int copy_length = source_length - position;
        if (dest_length < copy_length)
            copy_length = dest_length;

        for (int i = 0; i < copy_length; i++)
            dest[i] = source[position + i];

        // remember where to continue next time the copy_buffer() is called
        position += copy_length;

        return copy_length;            
    }
}

Есть некоторые очевидные различия. Версия C++ выглядит немного менее многословной. Часть этого иллюзорна; эквивалент close_handle_buffer_traverse теперь соответствует delete объекту C++. Конечно, delete не является частью реализации класса TRAVERSAL, delete поставляется с языком.

В версии C++ нет "непрозрачного" дескриптора.

Версия C является более явной и, возможно, делает более очевидными, какие операции выполняются аппаратным обеспечением в ответ на выполнение программы.

Версия C более приспособлена для использования приведения к HANDLE_TYPE для создания "непрозрачного идентификатора", а не типа указателя. Версию C++ можно было бы «обернуть» в API, который делал то же самое, добавляя еще один уровень. В текущем примере пользователи этого класса будут поддерживать копию TRAVERSAL *, которая не совсем "непрозрачна".

В функции copy_buffer() версии C++ не нужно упоминать указатель trav, поскольку вместо этого он неявно разыменовывает предоставленный компилятором указатель this.

sizeof(TRAVERSAL) должно быть одинаковым как для примеров C, так и для C++ - без vtable, а также при условии, что идентификация типа во время выполнения для C++ отключена, класс C++ содержит только ту же структуру памяти, что и структура C в нашем первом примере. .

Стиль «непрозрачный идентификатор» в C++ используется реже, поскольку штраф за «прозрачность» в C++ снижен. Членами данных class TRAVERSAL являются private, поэтому TRAVERSAL * нельзя случайно использовать для нарушения нашего контракта API с пользователем API.

Обратите внимание, что как непрозрачный идентификатор, так и указатель класса уязвимы для злоупотреблений со стороны злонамеренного пользователя API — либо непрозрачный идентификатор, либо указатель класса могут быть напрямую приведены, например, к double **, позволяя владельцу идентификатора изменить член source. напрямую через память. Конечно, вы уже должны доверять вызывающему API, потому что в этом случае код вызова API находится в том же адресном пространстве. В примере сетевого файлового сервера могут возникнуть проблемы с безопасностью, если «непрозрачный идентификатор», основанный на адресе памяти, будет раскрыт снаружи.

Обычно я не стал бы вдаваться в подробности о доверии пользователя API, но хочу пояснить, что ключевое слово C++ private не имеет «правоприменения», оно только указывает на соглашение между программистами, которое также соблюдается компилятором, если иное не указано человеком. .

Наконец, указатель класса C++ можно преобразовать в непрозрачный идентификатор следующим образом:

typedef intptr_t HANDLE_TYPE;

HANDLE_TYPE init_buffer_traverse(double *src, size_t src_len)
{
    return (HANDLE_TYPE)(new TRAVERSAL(src, src_len));
}

int copy_buffer(HANDLE_TYPE h_traverse, double * dest, size_t dest_len)
{
    return ((TRAVERSAL *)h_traverse)->copy_buffer(dest, dest_len);
}

void close_handle_buffer_traverse(HANDLE_TYPE h)
{
    delete ((TRAVERSAL *)h);
}

И теперь наша краткость «эквивалентного» C++ может быть подвергнута дальнейшему сомнению.

То, что я написал о старом стиле программирования на C, относящемся к C++, не означало, что C++ лучше подходит для этой задачи. Я имею в виду только то, что инкапсуляция данных и сокрытие деталей реализации могут быть выполнены в C с помощью стиля, почти изоморфного стилю C++. Это может быть полезно знать, если вы программируете на C, но, к сожалению, сначала изучили C++.

PS

Я только что заметил, что наша реализация на сегодняшний день использовала:

dest[i] = (float)source[position + i];

при копировании байтов. Поскольку и dest, и source равны double * (то есть оба они указывают на значения double), здесь нет необходимости в приведении. Кроме того, приведение от double к float может привести к потере цифр точности в представлении с плавающей запятой. Так что это лучше всего удалить и переформулировать как:

dest[i] = source[position + i];
person Heath Hunnicutt    schedule 15.02.2010
comment
Ух ты! Это именно то, что я искал. Большое спасибо =) Вы упомянули, что это можно сделать лучше на C++; Если я когда-нибудь реализую это на C++. Что я должен искать? Есть ли у него имя в мире C++? - person Louise; 16.02.2010
comment
Пожалуйста. Это можно сделать аналогичным образом на C++ - какой способ лучше, зависит от ваших субъективных предпочтений. Но, судя по характеру ваших вопросов, я считаю, что вы изучаете программирование, взламывая низкоуровневые проекты — я думаю, замечательный подход. Для этого подхода я бы предложил сосредоточиться на C, пока вы не станете очень гибкими с ним. В этот момент переход на C++ безопаснее, потому что вы уже изучили более низкоуровневые вещи, которые проще сделать на C, чем на C++. Изучение C++ до C может привести к тому, что программисты неправильно поймут C, IMO. - person Heath Hunnicutt; 16.02.2010

Я начал смотреть на это, но вы, вероятно, могли бы сделать это так же хорошо: libsndfile имеет открытый исходный код, поэтому можно посмотреть, как работает sf_read_float(), и создать функцию, которая делает то же самое из буфера. http://www.mega-nerd.com/libsndfile/ содержит ссылку для скачивания.

person wallyk    schedule 14.02.2010
comment
Я смотрел на это, но это очень сложно. Каким-то образом он сохраняет позицию в обработчике, чтобы при следующем вызове функции она продолжила работу с того места, где остановилась. Сейчас пишу свою с нуля. Надеюсь, я не занимаюсь изобретением велосипеда. - person Louise; 15.02.2010