Как читать из буфера с обратной связью, чтобы не переполнение буфера? - PullRequest
2 голосов
/ 14 февраля 2010

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

#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.

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

Ответы [ 2 ]

2 голосов
/ 15 февраля 2010

Если вы хотите создать уникальные дескрипторы, вы можете сделать это с помощью 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 struct в нашем первом примере.

Реже использовать стиль "непрозрачный идентификатор" в 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];
0 голосов
/ 14 февраля 2010

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

...