Точная синхронизация потоков POSIX с использованием NDK

Я пишу простое аудиоприложение NDK OpenSL ES, которое записывает прикосновения пользователей к клавиатуре виртуального пианино, а затем воспроизводит их навсегда в заданном цикле. После долгих экспериментов и чтения я решил использовать для этого отдельный цикл POSIX. Как вы можете видеть в коде, он вычитает любое время обработки из времени ожидания, чтобы сделать интервал каждого цикла как можно ближе к желаемому интервалу ожидания (в данном случае это 5000000 наносекунд).

void init_timing_loop() {
    pthread_t fade_in;
    pthread_create(&fade_in, NULL, timing_loop, (void*)NULL);
}

void* timing_loop(void* args) {

    while (1) {

        clock_gettime(CLOCK_MONOTONIC, &timing.start_time_s);

        tic_counter(); // simple logic gates that cycle the current tic
        play_all_parts(); // for-loops through all parts and plays any notes (From an OpenSL buffer) that fall on the current tic

        clock_gettime(CLOCK_MONOTONIC, &timing.finish_time_s);

        timing.diff_time_s.tv_nsec = (5000000 - (timing.finish_time_s.tv_nsec - timing.start_time_s.tv_nsec));

        nanosleep(&timing.diff_time_s, NULL);
    }

    return NULL;
}

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

Есть ли лучший способ приблизиться к этому? Для отладки я запустил следующий код:

gettimeofday(&timing.curr_time, &timing.tzp);
__android_log_print(ANDROID_LOG_DEBUG, "timing_loop", "gettimeofday: %d %d",
    timing.curr_time.tv_sec, timing.curr_time.tv_usec);

Это дает довольно последовательные показания, которые совершенно не отражают неточности воспроизведения. Существуют ли какие-то другие силы, действующие на Android, препятствующие точному времени? Или OpenSL ES является потенциальной проблемой? Все данные буфера загружаются в память - могут ли быть там узкие места?

С удовольствием опубликую больше кода OpenSL, если это необходимо... но на данном этапе я пытаюсь выяснить, является ли этот цикл потока точным или есть лучший способ сделать это.


person Michael J Petrie    schedule 14.06.2013    source источник
comment
Одна мысль: всегда держите аудиобуфер полным. Заполните паузы, воспроизведя соответствующее количество сэмплов тишины. Таким образом, все синхронизируется с аудиочасами, и переменные задержки не повлияют на вас (пока вы держите буферы заполненными).   -  person fadden    schedule 14.06.2013
comment
Спасибо за ваш ответ, кажется, это лучший способ пойти. Вопрос с синхронизацией на основе буфера: для полифонии мне нужно будет загружать до 16 буферных очередей одновременно. Если я буду постоянно держать их полными, как вы думаете, они будут синхронизированы? Я все равно проверю это, но подумал, что спрошу, если у вас есть опыт в этом.   -  person Michael J Petrie    schedule 14.06.2013
comment
В этом потоке затрагивается аналогичная проблема, но с iOS, если кому-то интересно.   -  person Michael J Petrie    schedule 14.06.2013


Ответы (1)


Вы также должны учитывать секунды при использовании clock_gettime, вы можете получить больше timing.start_time_s.tv_nsec, чем timing.finish_time_s.tv_nsec. tv_nsec начинается с нуля при увеличении tv_sec.

timing.diff_time_s.tv_nsec =
(5000000 - (timing.finish_time_s.tv_nsec - timing.start_time_s.tv_nsec));

попробуйте что-то вроде

#define NS_IN_SEC 1000000000
(timing.finish_time_s.tv_sec * NS_IN_SEC + timing.finish_time_s.tv_nsec) -
(timing.start_time_s.tv_nsec * NS_IN_SEC + timing.start_time_s.tv_nsec)
person auselen    schedule 14.06.2013
comment
Спасибо за это, я обновлю свой код. Я решил использовать синхронизацию на основе буфера для воспроизведения звука, но по-прежнему буду использовать грубую нить для громкости/панорамирования и т. д. - person Michael J Petrie; 16.06.2013